Add PEP8 check and fix related issues

- Add PEP8 section to tox.ini
- Add hacking to requirements to enforce OpenStack style requirements
- Change setup.py to use PBR
- Add setup.cfg
- Fix formatting issues flagged by flake8 check
- Add copyright notices to all remaining files
- Update .gitignore file
- Fix bug in expression.py where a variable was set incorrectly

Change-Id: I634adba3a44b2bcebb4d8c5620cbade28c6c489a
This commit is contained in:
Levi Blackstone 2015-05-05 10:19:53 -05:00
parent 686f7063e4
commit fb16776f9d
11 changed files with 243 additions and 162 deletions

5
.gitignore vendored
View File

@ -28,3 +28,8 @@ nosetests.xml
# Translations # Translations
*.mo *.mo
# IDE Project Files
*.project
*.pydev*
*.idea

View File

@ -1 +1,3 @@
-e . hacking>=0.10.0,<0.11
ply
six>=1.5.2

View File

@ -1,3 +1,25 @@
[metadata] [metadata]
description-file = README.md description-file = README.md
name = timex
version = 0.21
author = Monsyne Dragon
author_email = mdragon@rackspace.com
summary = A time expressions library implementing a mini-language for manipulating datetimes
license = Apache-2
keywords =
datetime
manipulation
transformation
DSL
classifiers =
Development Status :: 3 - Alpha
License :: OSI Approved :: Apache Software License
Operating System :: POSIX :: Linux
Programming Language :: Python :: 2.6
Programming Language :: Python :: 2.7
home-page = https://github.com/stackforge/stacktach-timex
[files]
packages =
timex

View File

@ -1,49 +1,8 @@
import os #!/usr/bin/env python
from setuptools import setup, find_packages
from setuptools import setup
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
desc = """timex
=====
A time expressions library implementing a mini-language for manipulating
datetimes.
Much like regular expressions provide a mini-language for performing certain
operation on strings, Timex's time expressions provide a convenient way of
expressing datetime and date-range operations. These expressions are strings,
and can be safely read from a config file or user input.
Read README.md for syntax and examples.
"""
setup( setup(
name='timex', setup_requires=['pbr'],
version='0.20.0', pbr=True,
author='Monsyne Dragon',
author_email='mdragon@rackspace.com',
description=("A time expressions library implementing a mini-language "
"for manipulating datetimes"),
license='Apache License (2.0)',
keywords='datetime manipulation transformation DSL',
packages=find_packages(exclude=['tests']),
classifiers=[
'Development Status :: 3 - Alpha',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
],
url='https://github.com/stackforge/stacktach-timex',
scripts=[],
long_description=desc,
install_requires=[
"ply",
"six >= 1.5.2",
],
zip_safe=False
) )

View File

@ -1,19 +1,33 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime import datetime
#for Python2.6 compatability. # for Python2.6 compatability.
import unittest2 as unittest import unittest2 as unittest
import mock
import six
from timex import expression from timex import expression
class TestTimestamp(unittest.TestCase): class TestTimestamp(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestTimestamp, self).setUp() super(TestTimestamp, self).setUp()
self.dt = datetime.datetime(2014, 8, 1, 2 ,10, 23, 550) self.dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
self.other_dt = datetime.datetime(2014, 8, 7, 2 ,0, 0, 0) self.other_dt = datetime.datetime(2014, 8, 7, 2, 0, 0, 0)
self.timestamp = expression.Timestamp(self.dt) self.timestamp = expression.Timestamp(self.dt)
def test_timestamp_properties(self): def test_timestamp_properties(self):
@ -101,14 +115,18 @@ class TestTimestamp(unittest.TestCase):
self.assertEqual(res.timestamp, expected) self.assertEqual(res.timestamp, expected)
expected = datetime.datetime(2014, 8, 1, 0, 0, 0, 0) expected = datetime.datetime(2014, 8, 1, 0, 0, 0, 0)
res = self.timestamp % expression.Duration(hour=0, minute=0, second=0, microsecond=0) res = self.timestamp % expression.Duration(hour=0, minute=0, second=0,
microsecond=0)
self.assertEqual(res.timestamp, expected) self.assertEqual(res.timestamp, expected)
def test_handle_ambig_duration(self): def test_handle_ambig_duration(self):
d = expression.Duration(hour=10, unknown=2) d = expression.Duration(hour=10, unknown=2)
self.assertRaises(expression.TimexExpressionError, self.timestamp.__add__, d) self.assertRaises(expression.TimexExpressionError,
self.assertRaises(expression.TimexExpressionError, self.timestamp.__sub__, d) self.timestamp.__add__, d)
self.assertRaises(expression.TimexExpressionError, self.timestamp.__mod__, d) self.assertRaises(expression.TimexExpressionError,
self.timestamp.__sub__, d)
self.assertRaises(expression.TimexExpressionError,
self.timestamp.__mod__, d)
def test_total_seconds(self): def test_total_seconds(self):
self.assertFalse(self.timestamp.is_range) self.assertFalse(self.timestamp.is_range)
@ -116,13 +134,12 @@ class TestTimestamp(unittest.TestCase):
class TestTimeRange(unittest.TestCase): class TestTimeRange(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestTimeRange, self).setUp() super(TestTimeRange, self).setUp()
self.begin_dt = datetime.datetime(2014, 8, 1, 2 ,10, 23, 550) self.begin_dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
self.end_dt = datetime.datetime(2014, 8, 2, 2 ,10, 23, 550) self.end_dt = datetime.datetime(2014, 8, 2, 2, 10, 23, 550)
self.middle_dt = datetime.datetime(2014, 8, 1, 17 ,30, 10, 25) self.middle_dt = datetime.datetime(2014, 8, 1, 17, 30, 10, 25)
self.other_dt = datetime.datetime(2014, 8, 7, 2 ,0, 0, 0) self.other_dt = datetime.datetime(2014, 8, 7, 2, 0, 0, 0)
self.timerange = expression.TimeRange(self.begin_dt, self.end_dt) self.timerange = expression.TimeRange(self.begin_dt, self.end_dt)
def test_timerange_properties(self): def test_timerange_properties(self):
@ -131,20 +148,20 @@ class TestTimeRange(unittest.TestCase):
self.assertEqual(self.begin_dt, self.timerange.timestamp) self.assertEqual(self.begin_dt, self.timerange.timestamp)
def test_match(self): def test_match(self):
#ranges include the beginning. # ranges include the beginning.
self.assertTrue(self.timerange.match(self.begin_dt)) self.assertTrue(self.timerange.match(self.begin_dt))
self.assertTrue(self.timerange.match(self.middle_dt)) self.assertTrue(self.timerange.match(self.middle_dt))
#ranges *don`t* include the end. # ranges *don`t* include the end.
self.assertFalse(self.timerange.match(self.end_dt)) self.assertFalse(self.timerange.match(self.end_dt))
self.assertFalse(self.timerange.match(self.other_dt)) self.assertFalse(self.timerange.match(self.other_dt))
def test_in(self): def test_in(self):
#ranges include the beginning. # ranges include the beginning.
self.assertTrue(self.begin_dt in self.timerange) self.assertTrue(self.begin_dt in self.timerange)
self.assertTrue(self.middle_dt in self.timerange) self.assertTrue(self.middle_dt in self.timerange)
#ranges *don`t* include the end. # ranges *don`t* include the end.
self.assertFalse(self.end_dt in self.timerange) self.assertFalse(self.end_dt in self.timerange)
self.assertFalse(self.other_dt in self.timerange) self.assertFalse(self.other_dt in self.timerange)
@ -234,7 +251,6 @@ class TestTimeRange(unittest.TestCase):
self.assertEqual(res.begin, expected_begin) self.assertEqual(res.begin, expected_begin)
self.assertEqual(res.end, expected_end) self.assertEqual(res.end, expected_end)
def test_replace(self): def test_replace(self):
expected_begin = datetime.datetime(2014, 8, 1, 6, 10, 23, 550) expected_begin = datetime.datetime(2014, 8, 1, 6, 10, 23, 550)
expected_end = datetime.datetime(2014, 8, 2, 6, 10, 23, 550) expected_end = datetime.datetime(2014, 8, 2, 6, 10, 23, 550)
@ -256,7 +272,8 @@ class TestTimeRange(unittest.TestCase):
expected_begin = datetime.datetime(2014, 8, 1, 0, 0, 0, 0) expected_begin = datetime.datetime(2014, 8, 1, 0, 0, 0, 0)
expected_end = datetime.datetime(2014, 8, 2, 0, 0, 0, 0) expected_end = datetime.datetime(2014, 8, 2, 0, 0, 0, 0)
res = self.timerange % expression.Duration(hour=0, minute=0, second=0, microsecond=0) res = self.timerange % expression.Duration(hour=0, minute=0, second=0,
microsecond=0)
self.assertEqual(res.begin, expected_begin) self.assertEqual(res.begin, expected_begin)
self.assertEqual(res.end, expected_end) self.assertEqual(res.end, expected_end)
@ -283,17 +300,16 @@ class TestTimeRange(unittest.TestCase):
def test_total_seconds(self): def test_total_seconds(self):
self.assertTrue(self.timerange.is_range) self.assertTrue(self.timerange.is_range)
self.assertEqual(self.timerange.total_seconds(), 24*60*60) self.assertEqual(self.timerange.total_seconds(), 24 * 60 * 60)
class TestPinnedTimeRange(unittest.TestCase): class TestPinnedTimeRange(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestPinnedTimeRange, self).setUp() super(TestPinnedTimeRange, self).setUp()
self.begin_dt = datetime.datetime(2014, 8, 1, 1, 0, 0, 0) self.begin_dt = datetime.datetime(2014, 8, 1, 1, 0, 0, 0)
self.end_dt = datetime.datetime(2014, 8, 2, 1, 0, 0, 0) self.end_dt = datetime.datetime(2014, 8, 2, 1, 0, 0, 0)
self.middle_dt = datetime.datetime(2014, 8, 1, 17 ,30, 10, 25) self.middle_dt = datetime.datetime(2014, 8, 1, 17, 30, 10, 25)
self.other_dt = datetime.datetime(2014, 8, 7, 2 ,0, 0, 0) self.other_dt = datetime.datetime(2014, 8, 7, 2, 0, 0, 0)
self.timerange = expression.PinnedTimeRange(self.begin_dt, self.timerange = expression.PinnedTimeRange(self.begin_dt,
self.end_dt, self.end_dt,
self.middle_dt, self.middle_dt,
@ -340,7 +356,6 @@ class TestPinnedTimeRange(unittest.TestCase):
class TestDuration(unittest.TestCase): class TestDuration(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestDuration, self).setUp() super(TestDuration, self).setUp()
self.second = expression.Duration(second=1) self.second = expression.Duration(second=1)
@ -377,4 +392,3 @@ class TestDuration(unittest.TestCase):
self.assertEqual(dd[unit], 1) self.assertEqual(dd[unit], 1)
for unit in ('microsecond', 'minute', 'month', 'year', 'unknown'): for unit in ('microsecond', 'minute', 'month', 'year', 'unknown'):
self.assertNotIn(unit, dd) self.assertNotIn(unit, dd)

View File

@ -1,18 +1,32 @@
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import datetime import datetime
#for Python2.6 compatability. # for Python2.6 compatability.
import unittest2 as unittest import unittest2 as unittest
import mock
import six
import timex import timex
class TestParse(unittest.TestCase): class TestParse(unittest.TestCase):
def setUp(self): def setUp(self):
super(TestParse, self).setUp() super(TestParse, self).setUp()
self.dt = datetime.datetime(2014, 8, 1, 2 ,10, 23, 550) self.dt = datetime.datetime(2014, 8, 1, 2, 10, 23, 550)
self.other_dt = datetime.datetime(2014, 8, 7, 3, 20, 0, 0) self.other_dt = datetime.datetime(2014, 8, 7, 3, 20, 0, 0)
def test_var(self): def test_var(self):
@ -22,21 +36,21 @@ class TestParse(unittest.TestCase):
self.assertEqual(t.timestamp, self.dt) self.assertEqual(t.timestamp, self.dt)
def test_timestamp_add(self): def test_timestamp_add(self):
result = datetime.datetime(2014, 8, 2, 4 ,10, 23, 550) result = datetime.datetime(2014, 8, 2, 4, 10, 23, 550)
exp = timex.parse("$test_thingy + 1d 2h") exp = timex.parse("$test_thingy + 1d 2h")
t = exp(test_thingy=self.dt) t = exp(test_thingy=self.dt)
self.assertFalse(t.is_range) self.assertFalse(t.is_range)
self.assertEqual(t.timestamp, result) self.assertEqual(t.timestamp, result)
def test_timestamp_sub(self): def test_timestamp_sub(self):
result = datetime.datetime(2014, 7, 31, 0 ,10, 23, 550) result = datetime.datetime(2014, 7, 31, 0, 10, 23, 550)
exp = timex.parse("$test_thingy - 1d 2h") exp = timex.parse("$test_thingy - 1d 2h")
t = exp(test_thingy=self.dt) t = exp(test_thingy=self.dt)
self.assertFalse(t.is_range) self.assertFalse(t.is_range)
self.assertEqual(t.timestamp, result) self.assertEqual(t.timestamp, result)
def test_timestamp_replace(self): def test_timestamp_replace(self):
result = datetime.datetime(2014, 8, 7, 6 ,10, 23, 550) result = datetime.datetime(2014, 8, 7, 6, 10, 23, 550)
exp = timex.parse("$test_thingy @ 7d 6h") exp = timex.parse("$test_thingy @ 7d 6h")
t = exp(test_thingy=self.dt) t = exp(test_thingy=self.dt)
self.assertFalse(t.is_range) self.assertFalse(t.is_range)
@ -50,7 +64,7 @@ class TestParse(unittest.TestCase):
self.assertEqual(t.end, self.other_dt) self.assertEqual(t.end, self.other_dt)
def test_timerange_add(self): def test_timerange_add(self):
result_begin = datetime.datetime(2014, 8, 2, 4 ,10, 23, 550) result_begin = datetime.datetime(2014, 8, 2, 4, 10, 23, 550)
result_end = datetime.datetime(2014, 8, 8, 5, 20, 0, 0) result_end = datetime.datetime(2014, 8, 8, 5, 20, 0, 0)
exp = timex.parse("($test_thingy to $other) + 1d 2h") exp = timex.parse("($test_thingy to $other) + 1d 2h")
t = exp(test_thingy=self.dt, other=self.other_dt) t = exp(test_thingy=self.dt, other=self.other_dt)
@ -59,7 +73,7 @@ class TestParse(unittest.TestCase):
self.assertEqual(t.end, result_end) self.assertEqual(t.end, result_end)
def test_timerange_sub(self): def test_timerange_sub(self):
result_begin = datetime.datetime(2014, 7, 31, 0 ,10, 23, 550) result_begin = datetime.datetime(2014, 7, 31, 0, 10, 23, 550)
result_end = datetime.datetime(2014, 8, 6, 1, 20, 0, 0) result_end = datetime.datetime(2014, 8, 6, 1, 20, 0, 0)
exp = timex.parse("($test_thingy to $other) - 1d 2h") exp = timex.parse("($test_thingy to $other) - 1d 2h")
t = exp(test_thingy=self.dt, other=self.other_dt) t = exp(test_thingy=self.dt, other=self.other_dt)
@ -129,4 +143,3 @@ class TestParse(unittest.TestCase):
self.assertTrue(t.is_range) self.assertTrue(t.is_range)
self.assertEqual(t.begin, result_begin) self.assertEqual(t.begin, result_begin)
self.assertEqual(t.end, result_end) self.assertEqual(t.end, result_end)

View File

@ -1,7 +1,10 @@
from timex.expression import Duration
from timex.expression import PinnedTimeRange
from timex.expression import Timestamp
from timex.expression import TimeRange
from timex.expression import TimexExpressionError
from timex.expression import TimexParserError
from timex.expression import TimexLexerError
from timex.expression import TimexError
from timex.parser import parse from timex.parser import parse
from timex.expression import TimexExpressionError, TimexParserError
from timex.expression import TimexLexerError, TimexError
from timex.expression import Timestamp, TimeRange, PinnedTimeRange, Duration
__version__ = '0.10.0' __version__ = '0.10.0'

View File

@ -1,6 +1,23 @@
import logging #!/usr/bin/env python
import datetime # -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc import abc
import datetime
import logging
import six import six
@ -25,7 +42,6 @@ class TimexExpressionError(TimexError):
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class TimeMatcher(object): class TimeMatcher(object):
_allow_ambig_duration = False _allow_ambig_duration = False
@abc.abstractmethod @abc.abstractmethod
@ -60,11 +76,10 @@ class TimeMatcher(object):
def _check_duration(self, duration): def _check_duration(self, duration):
if isinstance(duration, Duration): if isinstance(duration, Duration):
if ((duration.ambiguous and self._allow_ambig_duration) if ((duration.ambiguous and self._allow_ambig_duration)
or not duration.ambiguous): or not duration.ambiguous):
return True return True
raise TimexExpressionError("Invalid duration for time operation") raise TimexExpressionError("Invalid duration for time operation")
def _dt_replace(self, dt, duration): def _dt_replace(self, dt, duration):
return dt.replace(**duration.as_dict) return dt.replace(**duration.as_dict)
@ -73,15 +88,16 @@ class TimeMatcher(object):
months = d.pop('month', 0) months = d.pop('month', 0)
years = d.pop('year', 0) years = d.pop('year', 0)
if d: if d:
delta = datetime.timedelta(**dict((k+"s",val) for k, val in d.items())) delta = datetime.timedelta(
**dict((k + "s", val) for k, val in d.items()))
dt = dt + delta dt = dt + delta
if months: if months:
newmonth = dt.month + months newmonth = dt.month + months
years += (newmonth - 1) // 12 years += (newmonth - 1) // 12
newmonth = ((newmonth-1) % 12) + 1 newmonth = ((newmonth - 1) % 12) + 1
dt = dt.replace(month=newmonth) dt = dt.replace(month=newmonth)
if years: if years:
dt = dt.replace(year=(dt.year+years)) dt = dt.replace(year=(dt.year + years))
return dt return dt
def _dt_sub(self, dt, duration): def _dt_sub(self, dt, duration):
@ -89,21 +105,21 @@ class TimeMatcher(object):
months = d.pop('month', 0) months = d.pop('month', 0)
years = d.pop('year', 0) years = d.pop('year', 0)
if d: if d:
delta = datetime.timedelta(**dict((k+"s",val) for k, val in d.items())) delta = datetime.timedelta(
**dict((k + "s", val) for k, val in d.items()))
dt = dt - delta dt = dt - delta
if months: if months:
newmonth = dt.month - months newmonth = dt.month - months
years -= (newmonth - 1) // 12 years -= (newmonth - 1) // 12
newmonth = ((newmonth-1) % 12) + 1 newmonth = ((newmonth - 1) % 12) + 1
dt = dt.replace(month=newmonth) dt = dt.replace(month=newmonth)
if years: if years:
dt = dt.replace(year=(dt.year-years)) dt = dt.replace(year=(dt.year - years))
return dt return dt
class Timestamp(TimeMatcher): class Timestamp(TimeMatcher):
"""This is a wrapper on a datetime that has the same """Wrapper on a datetime with same interface as TimeRange"""
interface as TimeRange"""
def __init__(self, dt): def __init__(self, dt):
self.timestamp = dt self.timestamp = dt
@ -136,7 +152,6 @@ class Timestamp(TimeMatcher):
class TimeRange(TimeMatcher): class TimeRange(TimeMatcher):
_allow_ambig_duration = True _allow_ambig_duration = True
def __init__(self, begin, end): def __init__(self, begin, end):
@ -149,7 +164,8 @@ class TimeRange(TimeMatcher):
def total_seconds(self): def total_seconds(self):
delta = self.end - self.begin delta = self.end - self.begin
return delta.seconds + (delta.days * 24 * 3600) + (delta.microseconds * 1e-6) return (delta.seconds + (delta.days * 24 * 3600) +
(delta.microseconds * 1e-6))
def __nonzero__(self): def __nonzero__(self):
return self.total_seconds() > 0 return self.total_seconds() > 0
@ -159,7 +175,10 @@ class TimeRange(TimeMatcher):
return True return True
def match(self, dt): def match(self, dt):
"""TimeRanges match datetimes from begin (inclusive) to end (exclusive)""" """Match datetimes
TimeRanges match datetimes from begin (inclusive) to end (exclusive)
"""
return dt >= self.begin and dt < self.end return dt >= self.begin and dt < self.end
def __add__(self, other): def __add__(self, other):
@ -236,11 +255,11 @@ class PinnedTimeRange(TimeRange):
return self.time_range return self.time_range
def __repr__(self): def __repr__(self):
return "PinnedTimeRange from %r to %r. Pinned to %s(%r)" % (self.begin, self.end, self.unit, self.pinned_to) return ("PinnedTimeRange from %r to %r. Pinned to %s(%r)"
% (self.begin, self.end, self.unit, self.pinned_to))
class Environment(dict): class Environment(dict):
def time_func_hour(self, timestamp): def time_func_hour(self, timestamp):
dt = timestamp.timestamp dt = timestamp.timestamp
begin = dt.replace(minute=0, second=0, microsecond=0) begin = dt.replace(minute=0, second=0, microsecond=0)
@ -261,20 +280,20 @@ class Environment(dict):
def time_func_year(self, timestamp): def time_func_year(self, timestamp):
dt = timestamp.timestamp dt = timestamp.timestamp
begin = dt.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) begin = dt.replace(month=1, day=1, hour=0, minute=0, second=0,
microsecond=0)
end = Timestamp(begin) + Duration(year=1) end = Timestamp(begin) + Duration(year=1)
return PinnedTimeRange(begin, end.timestamp, dt, 'year') return PinnedTimeRange(begin, end.timestamp, dt, 'year')
@six.add_metaclass(abc.ABCMeta) @six.add_metaclass(abc.ABCMeta)
class TimeExpression(object): class TimeExpression(object):
@abc.abstractmethod @abc.abstractmethod
def apply(self, env): def apply(self, env):
"""Apply the expression to a given set of arguments. """Apply the expression to a given set of arguments.
:param env: a dictionary-like object. expression functions should be methods :param env: a dictionary-like object. expression functions should
on this object with names beginning with 'time_func_' be methods on this object with names beginning with 'time_func_'
returns: TimeMatcher instance returns: TimeMatcher instance
""" """
@ -308,7 +327,8 @@ class TimeRangeFunction(TimeRangeExpression):
self.expr = expr self.expr = expr
def __repr__(self): def __repr__(self):
return '%s %s(%r)' % (self.__class__.__name__, self.func_name, self.expr) return ('%s %s(%r)'
% (self.__class__.__name__, self.func_name, self.expr))
def apply(self, env): def apply(self, env):
arg = self.expr.apply(env) arg = self.expr.apply(env)
@ -341,7 +361,8 @@ class Operation(TimeExpression):
self.duration = duration self.duration = duration
def __repr__(self): def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.expr, self.duration) return ('%s(%r, %r)'
% (self.__class__.__name__, self.expr, self.duration))
class Replace(Operation): class Replace(Operation):
@ -366,11 +387,10 @@ class Minus(Operation):
class Duration(object): class Duration(object):
UNIT_SIZES = {'year': 365 * 24 * 60 * 60,
UNIT_SIZES = {'year': 365*24*60*60, 'month': 28 * 24 * 60 * 60,
'month': 28*24*60*60, 'day': 24 * 60 * 60,
'day': 24*60*60, 'hour': 60 * 60,
'hour': 60*60,
'minute': 60, 'minute': 60,
'second': 1, 'second': 1,
'microsecond': 1e-6} 'microsecond': 1e-6}
@ -423,7 +443,7 @@ class Duration(object):
elif d >= self.UNIT_SIZES['minute']: elif d >= self.UNIT_SIZES['minute']:
unit = 'second' unit = 'second'
else: else:
unit = microsecond unit = 'microsecond'
vals = self.as_dict vals = self.as_dict
del vals['unknown'] del vals['unknown']
if unit in vals: if unit in vals:
@ -476,4 +496,3 @@ class Duration(object):
if our_val != other_val: if our_val != other_val:
return False return False
return True return True

View File

@ -1,5 +1,22 @@
import sys #!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging import logging
import sys
import ply.lex import ply.lex
@ -16,8 +33,8 @@ class TimexLexer(object):
self.debug = debug self.debug = debug
if not self.__doc__: if not self.__doc__:
raise TimexLexerError("Docstring information is missing. " raise TimexLexerError("Docstring information is missing. "
"Timex uses PLY which requires docstrings for " "Timex uses PLY which requires "
"configuration.") "docstrings for configuration.")
self.lexer = ply.lex.lex(module=self, self.lexer = ply.lex.lex(module=self,
debug=self.debug, debug=self.debug,
errorlog=logger) errorlog=logger)
@ -36,19 +53,20 @@ class TimexLexer(object):
token.col = token.lexpos - self.latest_newline token.col = token.lexpos - self.latest_newline
return token return token
reserved_words = { 'to' : 'TO', reserved_words = {
'us' : 'MICROSECOND', 'to': 'TO',
's' : 'SECOND', 'us': 'MICROSECOND',
'sec' : 'SECOND', 's': 'SECOND',
'm' : 'MINUTE', 'sec': 'SECOND',
'min' : 'MINUTE', 'm': 'MINUTE',
'h' : 'HOUR', 'min': 'MINUTE',
'hr' : 'HOUR', 'h': 'HOUR',
'd' : 'DAY', 'hr': 'HOUR',
'mo' : 'MONTH', 'd': 'DAY',
'y' : 'YEAR', 'mo': 'MONTH',
'yr' : 'YEAR', 'y': 'YEAR',
} 'yr': 'YEAR',
}
tokens = ('NUMBER', tokens = ('NUMBER',
'PLUS', 'PLUS',
@ -59,12 +77,12 @@ class TimexLexer(object):
'VAR', 'VAR',
'IDENTIFIER') + tuple(set(reserved_words.values())) 'IDENTIFIER') + tuple(set(reserved_words.values()))
t_PLUS = r'\+' t_PLUS = r'\+'
t_MINUS = r'-' t_MINUS = r'-'
t_REPLACE = r'@' t_REPLACE = r'@'
t_VAR = r'\$' t_VAR = r'\$'
t_LPAREN = r'\(' t_LPAREN = r'\('
t_RPAREN = r'\)' t_RPAREN = r'\)'
def t_IDENTIFIER(self, t): def t_IDENTIFIER(self, t):
r'[a-zA-Z_][a-zA-Z0-9_]*' r'[a-zA-Z_][a-zA-Z0-9_]*'
@ -76,13 +94,12 @@ class TimexLexer(object):
t.value = int(t.value) t.value = int(t.value)
return t return t
def t_newline(self, t): def t_newline(self, t):
r'\n+' r'\n+'
t.lexer.lineno += len(t.value) t.lexer.lineno += len(t.value)
self.latest_newline = t.lexpos self.latest_newline = t.lexpos
t_ignore = ' \t' t_ignore = ' \t'
def t_error(self, t): def t_error(self, t):
raise TimexLexerError('Error on line %s, col %s: Unexpected character:' raise TimexLexerError('Error on line %s, col %s: Unexpected character:'
@ -99,4 +116,3 @@ if __name__ == '__main__':
while token: while token:
print('%-20s%s' % (token.value, token.type)) print('%-20s%s' % (token.value, token.type))
token = lexer.token() token = lexer.token()

View File

@ -1,14 +1,34 @@
import sys, os #!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# Copyright © 2014 Rackspace Hosting.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import logging import logging
import os
import ply.yacc import ply.yacc
from timex.lexer import TimexLexer from timex.expression import Duration
from timex.expression import Minus
from timex.expression import Plus
from timex.expression import Replace
from timex.expression import TimeRangeExpression
from timex.expression import TimeRangeFunction
from timex.expression import TimexParserError from timex.expression import TimexParserError
from timex.expression import Replace, Plus, Minus from timex.expression import Variable
from timex.expression import Duration, Variable from timex.lexer import TimexLexer
from timex.expression import TimeRangeFunction, TimeRangeExpression
""" """
@ -53,7 +73,6 @@ unit : SECOND
""" """
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -62,7 +81,7 @@ def parse(string):
class TimexParser(object): class TimexParser(object):
""" LALR parser for time expression mini-language.""" """LALR parser for time expression mini-language."""
tokens = TimexLexer.tokens tokens = TimexLexer.tokens
def __init__(self, debug=False, lexer_class=None, start='time_expression'): def __init__(self, debug=False, lexer_class=None, start='time_expression'):
@ -70,15 +89,15 @@ class TimexParser(object):
self.start = start self.start = start
if not self.__doc__: if not self.__doc__:
raise TimexParserError("Docstring information is missing. " raise TimexParserError("Docstring information is missing. "
"Timex uses PLY which requires docstrings for " "Timex uses PLY which requires "
"configuration.") "docstrings for configuration.")
self.lexer_class = lexer_class or TimexLexer self.lexer_class = lexer_class or TimexLexer
def _parse_table(self): def _parse_table(self):
tabdir = os.path.dirname(__file__) tabdir = os.path.dirname(__file__)
try: try:
module_name = os.path.splitext(os.path.split(__file__)[1])[0] module_name = os.path.splitext(os.path.split(__file__)[1])[0]
except: except Exception:
module_name = __name__ module_name = __name__
table_module = '_'.join([module_name, self.start, 'parsetab']) table_module = '_'.join([module_name, self.start, 'parsetab'])
return (tabdir, table_module) return (tabdir, table_module)
@ -89,11 +108,11 @@ class TimexParser(object):
tabdir, table_module = self._parse_table() tabdir, table_module = self._parse_table()
parser = ply.yacc.yacc(module=self, parser = ply.yacc.yacc(module=self,
debug=self.debug, debug=self.debug,
tabmodule = table_module, tabmodule=table_module,
outputdir = tabdir, outputdir=tabdir,
write_tables=0, write_tables=0,
start = self.start, start=self.start,
errorlog = logger) errorlog=logger)
return parser.parse(string, lexer=lexer) return parser.parse(string, lexer=lexer)
@ -106,7 +125,7 @@ class TimexParser(object):
def p_error(self, t): def p_error(self, t):
raise TimexParserError('Parse error at %s:%s near token %s (%s)' % raise TimexParserError('Parse error at %s:%s near token %s (%s)' %
(t.lineno, t.col, t.value, t.type)) (t.lineno, t.col, t.value, t.type))
def p_time_expression(self, p): def p_time_expression(self, p):
"""time_expression : timerange_expression """time_expression : timerange_expression
@ -189,7 +208,8 @@ class TimexParser(object):
| HOUR | HOUR
| DAY | DAY
| MONTH | MONTH
| YEAR""" | YEAR
"""
unit = TimexLexer.reserved_words[p[1]] unit = TimexLexer.reserved_words[p[1]]
unit = unit.lower() unit = unit.lower()
p[0] = unit p[0] = unit

10
tox.ini
View File

@ -1,5 +1,5 @@
[tox] [tox]
envlist = py26,py27 envlist = py26,py27,pep8
[testenv] [testenv]
deps = deps =
@ -13,3 +13,11 @@ commands =
sitepackages = False sitepackages = False
[testenv:pep8]
commands =
flake8
[flake8]
ignore = H405
exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,timex/__init__.py
show-source = True