1. Marc-Alexandre Chan
  2. DailyPromptBot

Commits

Marc-Alexandre Chan  committed a55cc6d

Adding in util.py - this got left out of the initial commit

  • Participants
  • Parent commits b7cf1b7
  • Branches dev-minibot

Comments (0)

Files changed (1)

File minibot/util.py

View file
  • Ignore whitespace
+#-------------------------------------------------------------------------------
+# The Daily Prompt Mini-Bot - A Shut Up and Write Project
+# Author: Marc-Alexandre Chan <laogeodritt at arenthil.net>
+#-------------------------------------------------------------------------------
+#
+# Copyright (c) 2012 Marc-Alexandre Chan. Licensed under the GNU GPL version 3
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+#-------------------------------------------------------------------------------
+
+""" Extra utility classes. """
+
+from datetime import datetime, time as dt_time, date as dt_date, timedelta
+import time
+import re
+import copy
+
+class MarkdownMixin(object):
+    """ Mixin providing markdown syntax shortcut methods. """
+
+    def md_bold(self, text):
+        return ''.join['**', text, '**']
+
+    def md_italic(self, text):
+        return ''.join['_', text, '_']
+
+    def md_code(self, text):
+        """ Return text as inline code. """
+        return ''.join(['`', text, '`'])
+
+    def md_link(self, text, url=None):
+        if url:
+            return ''.join['[', text, '](', url, ')']
+        else:
+            return text
+
+    def md_codeblock(self, text):
+        """ Return lines of text as preformatted (code) markdown. """
+        cbtext = '    ' + text.replace('\n', '\n    ')
+        if cbtext.endswith('\n    '):
+            cbtext = cbtext[0:-5]
+        return cbtext
+
+    def md_bullet(self, items):
+        """ Format a list of items as a bulleted list. """
+        if len(items) == 0:
+            return ''
+        else:
+            return ''.join(['* ', '\n* '.join(items), '\n'])
+
+    def md_numbered(self, items):
+        """ Format a list of items as a numbered list. """
+        if len(items) == 0:
+            return ''
+        else:
+            return ''.join(['1. ', '\n1. '.join(items), '\n'])
+
+    def md_quote(self, text):
+        """ Return lines of text as a quote. """
+        cbtext = '> ' + text.replace('\n', '\n> ')
+        if cbtext.endswith('\n> '):
+            cbtext = cbtext[0:-3]
+        return cbtext
+
+class DateParseMixin(object):
+    """ Mixin for classes that need to parse user-entered dates in the format
+    specified by the private message commands (see the `CheckMessageEvent`). """
+    # date format: Y-m-d or Y/m/d. Y can be 2- or 4-digit, m and d can be 1- or
+    # 2-digit
+    _re_date = re.compile(
+        r'\s?(?P<year>\d{2}|\d{4})'
+        r'\s?(?P<sep>-|/)'
+        r'\s?(?P<month>\d{1,2})'
+        r'\s?(?P=sep)'
+        r'\s?(?P<day>\d{1,2})'
+        r'\s?')
+    # time format: hh:mm:ss (24-hour) or hh:mm:ssx (12-hour, x = am or pm).
+    # Seconds are optional.
+    _re_time = re.compile(
+        r'\s?(?P<h>\d{1,2})'
+        r'\s?:'
+        r'\s?(?P<min>\d{1,2})'
+        r'(?:\s?:'
+        r'\s?(?P<sec>?:\d{1,2}))?'
+        r'\s?(?P<time>am|pm)'
+        r'\s?',
+        re.IGNORECASE)
+
+    def _is_dst(self, datetime_):
+        """ Return true if DST applies in the system's local timezone at the
+        passed datetime. datetime_ is assumed to be in the system's local
+        timezone; timezone information is discarded. """
+        dt2 = copy(datetime_)
+        dt2.replace(tzinfo=None)
+        return (time.localtime(time.mktime(dt2.timetuple())).tm_isdst == 1)
+
+    def _parse_datetime(self, date_str=None, time_str=None):
+        """ Parses date and time strings into a datetime object. If date is
+        None, a `datetime.time` object is returned. If time is None, a
+        `datetime` object is returned with time at midnight. Throws
+        CommandParameterError on parse or value errors. """
+        r_date = None
+        r_time = None
+
+        # guard in case a proper object is passed
+        if isinstance(date_str, datetime):
+            r_date = date_str.date()
+            r_time = date_str.time() # don't output tzinfo
+            date_str = None
+            time_str = None
+        else:
+            if isinstance(date_str, dt_date):
+                r_date = date_str
+                date_str = None
+            if isinstance(time_str, dt_time):
+                r_time = time_str
+                time_str = None
+
+        # parses date/time using regexes---more flexible/tolerant than strptime
+        if date_str is not None:
+            date_match = self._re_date.match(date_str)
+            if date_match is not None:
+                year, month, day = date_match.group('year', 'month', 'day')
+            else:
+                raise CommandParameterError(
+                    ''.join(["Error parsing date parameter: '", date_str,
+                             "'."]))
+
+            try:
+                year  = int(year,  10)
+                month = int(month, 10)
+                day   = int(day,   10)
+            except ValueError:
+                raise CommandParameterError(
+                    ''.join(["Error parsing date parameter: '", date_str,
+                             "'."]))
+
+            # convert two-digit years
+            if year < 100:
+                if year >= 70:
+                    year += 1900
+                else:
+                    year += 2000
+
+            try:
+                r_date = dt_date(year, month, day)
+            except ValueError:
+                raise CommandParameterError(
+                        ''.join(["Invalid date: '", date_str, "'."]))
+
+        if time_str is not None:
+            time_match = self._re_time.match(time_str)
+            if time_match is not None:
+                hour, min, sec, am_pm =\
+                        time_match.group('h', 'min', 'sec', 'time')
+            else:
+                raise CommandParameterError(
+                    ''.join(["Error parsing time parameter: '", time_str,
+                             "'."]))
+
+            try:
+                hour = int(hour, 10)
+                min  = int(min, 10)
+                sec  = int(sec, 10) if sec else 0
+            except ValueError:
+                raise CommandParameterError(
+                    ''.join(["Error parsing time parameter: '", time_str,
+                             "'."]))
+
+            # adjust am/pm if present
+            if am_pm and am_pm.lower() == 'pm':
+                hour += 12
+
+            try:
+                r_time = dt_time(hour, min, sec)
+            except ValueError:
+                raise CommandParameterError(
+                        ''.join(["Invalid time: '", time_str, "'."]))
+
+        # figure out what to return based on generated objects
+        if r_date is not None and r_time is not None:
+            return datetime.combine(r_date, r_time)
+        elif r_date is not None:
+            return datetime.combine(r_date, dt_time())
+        elif r_time is not None:
+            return r_time
+        else:
+            return None
+
+    def _utctime(self, datetime_):
+        """ Adjust a naive local datetime object to UTC. """
+        if self._is_dst(datetime_):
+            tzoffset = time.altzone
+        else:
+            tzoffset = time.timezone
+        return (datetime_ + timedelta(seconds=tzoffset))
+
+    def _localtime(self, utc_datetime):
+        """ Adjusts a naive UTC datetime object to local time. """
+        if self._is_dst(utc_datetime):
+            tzoffset = time.altzone
+        else:
+            tzoffset = time.timezone
+        return (utc_datetime - timedelta(seconds=tzoffset))