Skip to content

Commit

Permalink
Merge branch 'dev' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
MoralCode committed May 27, 2021
2 parents e13bb63 + e8023a7 commit 49dc82e
Show file tree
Hide file tree
Showing 18 changed files with 146 additions and 41 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ jobs:
- name: Generate docs
run: |
pipenv run pdoc3 --html parse_opening_hours
mv html/parse_opening_hours.html html/index.html
pipenv run pdoc3 --html opening_hours
mv html/opening_hours.html html/index.html
- name: Deploy to GitHub Pages
if: success()
uses: crazy-max/ghaction-github-pages@v2
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Python Opening Hours parser

[![CircleCI](https://circleci.com/gh/MoralCode/jsonify-opening-hours.svg?style=shield)](https://circleci.com/gh/MoralCode/jsonify-opening-hours)
[![codecov](https://codecov.io/gh/MoralCode/jsonify-opening-hours/branch/main/graph/badge.svg?token=7JUFXSX43N)](https://codecov.io/gh/MoralCode/jsonify-opening-hours)
[![Downloads](https://pepy.tech/badge/jsonify-opening-hours/month)](https://pepy.tech/project/jsonify-opening-hours)
[![CircleCI](https://circleci.com/gh/MoralCode/parse-opening-hours.svg?style=shield)](https://circleci.com/gh/MoralCode/parse-opening-hours)
[![codecov](https://codecov.io/gh/MoralCode/parse-opening-hours/branch/main/graph/badge.svg?token=7JUFXSX43N)](https://codecov.io/gh/MoralCode/parse-opening-hours)
[![Downloads](https://pepy.tech/badge/parse-opening-hours/month)](https://pepy.tech/project/parse-opening-hours)

This library parses opening hours from various human-readable strings such as "Mon- Fri 9:00am - 5:30pm" into a more standard JSON format that can be processed more easily.

Expand All @@ -18,13 +18,13 @@ opening_hours = [
]
```
## Installation
`pip install jsonify-opening-hours`
`pip install parse-opening-hours`

## Usage

The simplest example is just printing the JSON for an opening hours string:
```python
from parse_opening_hours import OpeningHours
from opening_hours import OpeningHours

print(OpeningHours.parse("Mon- Fri 9:00am - 5:30pm").json())
```
Expand All @@ -42,7 +42,7 @@ This should give you the below output:

This has been tested using Python 3.8.5
### Documentation
In addition to this README, there is some documentation generated from inline documentation comments. This is available at htttps://moralcode.github.io/jsonify-opening-hours/
In addition to this README, there is some documentation generated from inline documentation comments. This is available at htttps://moralcode.github.io/parse-opening-hours/
### Environment variables
Setting the environment variable `OH_DEBUG` to a value of `Y` will set the root logging level to debug and will cause log entries to appear in stdout for debugging purposes

Expand Down
1 change: 1 addition & 0 deletions opening_hours/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .parse_opening_hours import OpeningHours, create_entry
4 changes: 4 additions & 0 deletions opening_hours/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# from day import *
# from days import *
# from time import *
# from times import *
3 changes: 2 additions & 1 deletion models/day.py → opening_hours/models/day.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import enum
from patterns import *
from opening_hours.patterns import *
import logging, os

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -31,6 +31,7 @@ def from_string(cls, day_string, assume_type=None):

day = day_string.lower()
# ignore anything after the "day" part, if present
logger.debug(day)
if "day" in day:
day = day.split("day")[0]
day += "day"
Expand Down
10 changes: 6 additions & 4 deletions models/days.py → opening_hours/models/days.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from models.day import Day, DaysEnum
from opening_hours.models.day import Day, DaysEnum
import logging, os

logger = logging.getLogger(__name__)
Expand All @@ -12,13 +12,13 @@ class Days():
"""
start_day = None
end_day = None

#TODO: support days with exceptions (like "Monday to friday but not thurdsays")
@classmethod
def from_shortcut_string(cls, days_string, assume_type=None):
"""
create a time object from a string
"""
logger.debug("creating days object from string: " + days_string)
logger.debug("creating days object from shortcut: " + days_string)
if days_string is None:
raise TypeError("Cannot create Days Object from value None")

Expand Down Expand Up @@ -61,6 +61,7 @@ def from_parse_results(cls, result):
# this is a date range that includes the intervening days
start_day = Day.from_string(result.get("startday")[0])
end_day = result.get("endday")[0]
logger.debug(end_day)
end_day = Day.from_string(end_day) if end_day is not None else end_day
days = cls(start_day, end_day)
elif "day" in result:
Expand All @@ -71,7 +72,8 @@ def from_parse_results(cls, result):
logger.info("shortcut date detected")
days = cls.from_shortcut_string(result.get( "day_shortcuts")[0])
else:
logger.info("unspecified date detected")
logger.info("unspecified date detected ")
# logger.debug(vars(result))
# nothing specified, assumeit means every day
return cls(DaysEnum.MONDAY, DaysEnum.SUNDAY)
return days
Expand Down
4 changes: 3 additions & 1 deletion models/time.py → opening_hours/models/time.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import enum
from patterns import *
from opening_hours.patterns import *
import logging, os

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -63,6 +63,8 @@ def from_shortcut(cls, string):
return cls(0,0,time_type=TimeType.MILITARY)
elif "noon" in string:
return cls(12,0,time_type=TimeType.MILITARY)
# elif "closed" in string:
# TODO: how to handle this???

raise ValueError("Cannot match given shortcut string '" + string + "' to a known time shortcut pattern")

Expand Down
5 changes: 4 additions & 1 deletion models/times.py → opening_hours/models/times.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from models.time import Time, TimeType
from opening_hours.models.time import Time, TimeType
import logging, os

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -113,6 +113,9 @@ def get_end_time(self):

#TODO: possibly add a function to see if a single Time is within the range
# specified by this Times object


#TODO: getduration function

def __str__(self):
return self.start_time + to + self.end_time
Expand Down
25 changes: 19 additions & 6 deletions parse_opening_hours.py → opening_hours/parse_opening_hours.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,11 @@
# ]

from pyparsing import Word, alphas, nums, oneOf, Optional, Or, OneOrMore, Char
from patterns import *
from models.day import Day, DaysEnum
from models.days import Days
from models.time import Time, TimeType
from models.times import Times
from opening_hours.patterns import *
from opening_hours.models.day import Day, DaysEnum
from opening_hours.models.days import Days
from opening_hours.models.time import Time, TimeType
from opening_hours.models.times import Times
import unicodedata
import os
import logging
Expand All @@ -34,6 +34,17 @@ def parse(cls, hours_string, assume_type=None):
"""This parse function allows an OpeningHours instance to be created from most arbitrary strings representing opening hours using pyparsing."""

hours_string = unicodedata.normalize('NFC', hours_string)
# TODO: handle unicode confuseables
# TODO: handle special cases taht apply to beoth data and time, like "24/7"

pattern = note+opening_hours_format
# Or([

# opening_hours_format + notes_end
# ])
logger.debug(hours_string)
for p in pattern.scanString(hours_string):
logger.debug(p)

hours_string = hours_string.strip()

Expand All @@ -42,7 +53,7 @@ def parse(cls, hours_string, assume_type=None):

def __init__(self, openinghours, assume_type=None):
self.openinghours = openinghours
self.assume_type = assume_type
self.assume_type = assume_type #temporary

def json(self, assume_type=None):
"""Converts the parsed results from pyparsing into the json output """
Expand All @@ -65,6 +76,7 @@ def json(self, assume_type=None):

return opening_hours_json

# TODO: normalize function to return the opening hours string as a string in a consistent, predictable format


def create_entry(day, opening, closing, notes=None):
Expand All @@ -79,3 +91,4 @@ def create_entry(day, opening, closing, notes=None):
return entry


# print(OpeningHours.parse("by appointment Sunday \u2013 Wednesday from 9 a.m. to 5 p.m."))
12 changes: 8 additions & 4 deletions patterns.py → opening_hours/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def caselessChar(some_str):
section_separator = Optional(",")
time_separator = Optional(":")

# TODO: use CaselessCloseMatch here once implemented to handle typos, particularly for the longer names
day = Combine(Or([
MatchFirst([
CaselessLiteral("Monday") + Optional(plural),
Expand Down Expand Up @@ -79,7 +80,7 @@ def caselessChar(some_str):
]),
]))


# TODO: use CaselessCloseMatch here once implemented to handle typos, particularly for the longer names
day_shortcuts = Combine(Or([
Or([
CaselessLiteral("Work"),
Expand Down Expand Up @@ -141,15 +142,18 @@ def caselessChar(some_str):

daterange = day.setResultsName('startday', listAllMatches=True) + range_separator + day.setResultsName('endday', listAllMatches=True)

dates = Optional(Or([daterange, dateList, dateShortcuts]))
dates = Or([daterange, dateList, dateShortcuts])

time = Group(Or([clocktime, time_shortcuts]))

timerange = time.setResultsName('starttime', listAllMatches=True) + Optional(range_separator + time.setResultsName('endtime', listAllMatches=True))

opening_hours_format = Or([
useless_optional_prefixes + OneOrMore(dates + day_time_separators + timerange),
useless_optional_prefixes + OneOrMore(Optional(dates) + day_time_separators + timerange),
useless_optional_prefixes + OneOrMore(timerange + dates)
])

notes = section_separator + Optional(OneOrMore(Word(alphas))).setResultsName('notes', listAllMatches=True)
note = Optional(Group(OneOrMore(caselessWord(alphas + " "), stopOn=opening_hours_format)).setResultsName('note', listAllMatches=True))

notes_start = note #+ FollowedBy(Or([dates, timerange]))
notes_end = FollowedBy(opening_hours_format) + note
7 changes: 5 additions & 2 deletions scripts/release.sh
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#!/bin/bash


git stash --include-untracked

rm -rf ./jsonify_opening_hours.egg-info/ build/

# make sure all tests pass
pipenv run pytest --cov=./

sleep 5

git stash --include-untracked

rm -rf dist/

python3 setup.py sdist bdist_wheel
Expand Down
9 changes: 4 additions & 5 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,20 @@

# This call to setup() does all the work
setup(
name="jsonify-opening-hours",
version="0.3.1",
name="parse-opening-hours",
version="0.4.0",
description="Parses opening hours from various human-readable strings into a standard JSON format",
long_description=README,
long_description_content_type="text/markdown",
url="https://github.com/MoralCode/jsonify-opening-hours",
url="https://github.com/MoralCode/parse-opening-hours",
author="Adrian Edwards",
author_email="[email protected]",
license="MIT",
classifiers=[
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
],
packages=[".", "models"],
packages=find_packages(exclude=["tests"]),
include_package_data=True,
install_requires=["pyparsing",],
)
Empty file added tests/__init__.py
Empty file.
2 changes: 1 addition & 1 deletion models/test_day.py → tests/test_day.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import unittest
from models.day import *
from opening_hours.models.day import *

class TestDay(unittest.TestCase):

Expand Down
37 changes: 35 additions & 2 deletions models/test_days.py → tests/test_days.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

import unittest
from models.days import *
from models.day import DaysEnum
from opening_hours.models.days import *
from opening_hours.models.day import DaysEnum


class TestDays(unittest.TestCase):
Expand Down Expand Up @@ -45,6 +45,39 @@ def test_create_from_none(self):
def test_create_from_unknown(self):
with self.assertRaises(ValueError):
Days.from_shortcut_string("cheeseburger")

# def test_from_parse_regular(self):
# test_dict = {
# "hour": 5,
# "minute": 0,
# "am_pm": "PM"
# }
# test_days_dict = Time.from_parse_results(test_dict)
# self.assertEqual(
# test_time_dict.hours,
# 5
# )
# self.assertEqual(test_time_dict.minutes, 0)

# self.assertTrue(test_time_dict.is_pm())

# def test_from_parse_shortcut(self):
# test_dict = {
# "time_shortcuts": "noon"
# }
# test_days_dict = Days.from_parse_results(test_dict)
# self.assertEqual(
# test_days_dict.hours,
# 12
# )
# self.assertEqual(test_days_dict.minutes, 0)

# def test_from_parse_unknown(self):
# test_dict = {
# "unknown": "dont care"
# }
# with self.assertRaises(ValueError):
# Days.from_parse_results(test_dict)

def test_workweek(self):
input_strings = [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import unittest
from parse_opening_hours import *
from opening_hours import *
from opening_hours.models.time import TimeType
import logging

logger = logging.getLogger(__name__)

class TestHoursParsing(unittest.TestCase):

Expand Down Expand Up @@ -213,8 +217,8 @@ def test_time_formatting(self):
"Monday 9-5",
"Monday 900-1700",
"Monday 0900-1700",
"Monday 09:00AM-05:00PM"

"Monday 09:00AM-05:00PM",
"Monday 900AM-500PM"
]
expected_result = [ self.mon_9_to_5 ]
self.run_tests(input_strings,expected_result, assume_type=TimeType.AM)
Expand Down
2 changes: 1 addition & 1 deletion models/test_time.py → tests/test_time.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import unittest
from models.time import *
from opening_hours.models.time import *

class TestTime(unittest.TestCase):

Expand Down
Loading

0 comments on commit 49dc82e

Please sign in to comment.