From adc01486ece020eb5c7690e8f688213834989ff8 Mon Sep 17 00:00:00 2001 From: zevav Date: Thu, 5 Sep 2019 09:02:20 +0200 Subject: [PATCH] first --- Pipfile | 14 ++ Pipfile.lock | 123 ++++++++++++++++++ README.rst | 1 + app/__init__.py | 0 app/add.py | 48 +++++++ app/cli.py | 11 ++ config.py | 1 + markdown_timesheet.egg-info/PKG-INFO | 19 +++ markdown_timesheet.egg-info/SOURCES.txt | 12 ++ .../dependency_links.txt | 1 + markdown_timesheet.egg-info/entry_points.txt | 4 + markdown_timesheet.egg-info/not-zip-safe | 1 + markdown_timesheet.egg-info/top_level.txt | 1 + setup.cfg | 2 + setup.py | 41 ++++++ tests/__init__.py | 0 tests/test_add.py | 71 ++++++++++ tests/timesheet.md | 50 +++++++ types_.py | 3 + 19 files changed, 403 insertions(+) create mode 100644 Pipfile create mode 100644 Pipfile.lock create mode 100644 README.rst create mode 100644 app/__init__.py create mode 100644 app/add.py create mode 100644 app/cli.py create mode 100644 config.py create mode 100644 markdown_timesheet.egg-info/PKG-INFO create mode 100644 markdown_timesheet.egg-info/SOURCES.txt create mode 100644 markdown_timesheet.egg-info/dependency_links.txt create mode 100644 markdown_timesheet.egg-info/entry_points.txt create mode 100644 markdown_timesheet.egg-info/not-zip-safe create mode 100644 markdown_timesheet.egg-info/top_level.txt create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 tests/__init__.py create mode 100644 tests/test_add.py create mode 100644 tests/timesheet.md create mode 100644 types_.py diff --git a/Pipfile b/Pipfile new file mode 100644 index 0000000..40172af --- /dev/null +++ b/Pipfile @@ -0,0 +1,14 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +pytest = "*" +python-dateutil = "*" +click = "*" + +[requires] +python_version = "3.7" diff --git a/Pipfile.lock b/Pipfile.lock new file mode 100644 index 0000000..53a214c --- /dev/null +++ b/Pipfile.lock @@ -0,0 +1,123 @@ +{ + "_meta": { + "hash": { + "sha256": "ecd9551140ee3d10e0a0efe9489ca58e21466e0ecbe6da511f95e3f6483c3e44" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.7" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "atomicwrites": { + "hashes": [ + "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4", + "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6" + ], + "version": "==1.3.0" + }, + "attrs": { + "hashes": [ + "sha256:69c0dbf2ed392de1cb5ec704444b08a5ef81680a61cb899dc08127123af36a79", + "sha256:f0b870f674851ecbfbbbd364d6b5cbdff9dcedbc7f3f5e18a6891057f21fe399" + ], + "version": "==19.1.0" + }, + "click": { + "hashes": [ + "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", + "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + ], + "index": "pypi", + "version": "==7.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:9ff1b1c5a354142de080b8a4e9803e5d0d59283c93aed808617c787d16768375", + "sha256:b7143592e374e50584564794fcb8aaf00a23025f9db866627f89a21491847a8d" + ], + "markers": "python_version < '3.8'", + "version": "==0.20" + }, + "more-itertools": { + "hashes": [ + "sha256:409cd48d4db7052af495b09dec721011634af3753ae1ef92d2b32f73a745f832", + "sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4" + ], + "version": "==7.2.0" + }, + "packaging": { + "hashes": [ + "sha256:a7ac867b97fdc07ee80a8058fe4435ccd274ecc3b0ed61d852d7d53055528cf9", + "sha256:c491ca87294da7cc01902edbe30a5bc6c4c28172b5138ab4e4aa1b9d7bfaeafe" + ], + "version": "==19.1" + }, + "pluggy": { + "hashes": [ + "sha256:0825a152ac059776623854c1543d65a4ad408eb3d33ee114dff91e57ec6ae6fc", + "sha256:b9817417e95936bf75d85d3f8767f7df6cdde751fc40aed3bb3074cbcb77757c" + ], + "version": "==0.12.0" + }, + "py": { + "hashes": [ + "sha256:64f65755aee5b381cea27766a3a147c3f15b9b6b9ac88676de66ba2ae36793fa", + "sha256:dc639b046a6e2cff5bbe40194ad65936d6ba360b52b3c3fe1d08a82dd50b5e53" + ], + "version": "==1.8.0" + }, + "pyparsing": { + "hashes": [ + "sha256:6f98a7b9397e206d78cc01df10131398f1c8b8510a2f4d97d9abd82e1aacdd80", + "sha256:d9338df12903bbf5d65a0e4e87c2161968b10d2e489652bb47001d82a9b028b4" + ], + "version": "==2.4.2" + }, + "pytest": { + "hashes": [ + "sha256:95d13143cc14174ca1a01ec68e84d76ba5d9d493ac02716fd9706c949a505210", + "sha256:b78fe2881323bd44fd9bd76e5317173d4316577e7b1cddebae9136a4495ec865" + ], + "index": "pypi", + "version": "==5.1.2" + }, + "python-dateutil": { + "hashes": [ + "sha256:7e6584c74aeed623791615e26efd690f29817a27c73085b78e4bad02493df2fb", + "sha256:c89805f6f4d64db21ed966fda138f8a5ed7a4fdbc1a8ee329ce1b74e3c74da9e" + ], + "index": "pypi", + "version": "==2.8.0" + }, + "six": { + "hashes": [ + "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c", + "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73" + ], + "version": "==1.12.0" + }, + "wcwidth": { + "hashes": [ + "sha256:3df37372226d6e63e1b1e1eda15c594bca98a22d33a23832a90998faa96bc65e", + "sha256:f4ebe71925af7b40a864553f761ed559b43544f8f71746c2d756c7fe788ade7c" + ], + "version": "==0.1.7" + }, + "zipp": { + "hashes": [ + "sha256:3718b1cbcd963c7d4c5511a8240812904164b7f381b647143a89d3b98f9bcd8e", + "sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335" + ], + "version": "==0.6.0" + } + }, + "develop": {} +} diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..eb1ae45 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +... diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/add.py b/app/add.py new file mode 100644 index 0000000..994ba4c --- /dev/null +++ b/app/add.py @@ -0,0 +1,48 @@ +from datetime import datetime, date, time +from dateutil import parser +from typing import List, Union, Tuple + +from types_ import Minutes + + +def subtract_times(first: time, second: time) -> Minutes: + return int( + ( + datetime.combine(datetime(1, 1, 1, 0, 0, 0), second) + - datetime.combine(date.today(), first) + ).seconds + / 60 + ) + + +def add_up_timesheet(timesheet_string: str) -> Minutes: + total_minutes = 0 + + for line in timesheet_string.split("\n"): + time_range_strings = find_time_ranges(line) + if time_range_strings: + for time_range_string in time_range_strings: + start_string, end_string = split_time_range_string(time_range_string) + start, end = map(parse_time, [start_string, end_string]) + total_minutes += subtract_times(start, end) + return total_minutes + + +def find_time_ranges(timesheet_line: str) -> Union[list, List[str]]: + remove_hashes_and_leading_trailing_spaces = timesheet_line.replace("#", "").strip() + try: + parse_time(remove_hashes_and_leading_trailing_spaces) + except ValueError: + return [] + else: + if ":" not in remove_hashes_and_leading_trailing_spaces: + return [] + return remove_hashes_and_leading_trailing_spaces.split(", ") + + +def parse_time(time_string: str) -> time: + return parser.parse(time_string).time() + + +def split_time_range_string(time_range_string) -> Tuple[str]: + return time_range_string.split("-") diff --git a/app/cli.py b/app/cli.py new file mode 100644 index 0000000..a6c537d --- /dev/null +++ b/app/cli.py @@ -0,0 +1,11 @@ +import click + +from app.add import add_up_timesheet + + +@click.command() +@click.argument("filename", type=click.Path(exists=True)) +def cli(filename): + with open(filename) as fin: + timesheet_string = fin.read() + click.echo(add_up_timesheet(timesheet_string)) diff --git a/config.py b/config.py new file mode 100644 index 0000000..0b0e4f4 --- /dev/null +++ b/config.py @@ -0,0 +1 @@ +SAMPLE_TIMESHEET_PATH = "tests/timesheet.md" diff --git a/markdown_timesheet.egg-info/PKG-INFO b/markdown_timesheet.egg-info/PKG-INFO new file mode 100644 index 0000000..8f6f488 --- /dev/null +++ b/markdown_timesheet.egg-info/PKG-INFO @@ -0,0 +1,19 @@ +Metadata-Version: 1.1 +Name: markdown-timesheet +Version: 0.1.7 +Summary: A quick way to add up time entries in a markdown document. +Home-page: https://github.com/zevaverbach/markdown_timesheet +Author: Zev Averbach +Author-email: zev@averba.ch +License: MIT license +Description: ... + +Keywords: markdown timesheet +Platform: UNKNOWN +Classifier: Development Status :: 2 - Pre-Alpha +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Natural Language :: English +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 diff --git a/markdown_timesheet.egg-info/SOURCES.txt b/markdown_timesheet.egg-info/SOURCES.txt new file mode 100644 index 0000000..306a507 --- /dev/null +++ b/markdown_timesheet.egg-info/SOURCES.txt @@ -0,0 +1,12 @@ +README.rst +setup.cfg +setup.py +app/__init__.py +app/add.py +app/cli.py +markdown_timesheet.egg-info/PKG-INFO +markdown_timesheet.egg-info/SOURCES.txt +markdown_timesheet.egg-info/dependency_links.txt +markdown_timesheet.egg-info/entry_points.txt +markdown_timesheet.egg-info/not-zip-safe +markdown_timesheet.egg-info/top_level.txt \ No newline at end of file diff --git a/markdown_timesheet.egg-info/dependency_links.txt b/markdown_timesheet.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/markdown_timesheet.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/markdown_timesheet.egg-info/entry_points.txt b/markdown_timesheet.egg-info/entry_points.txt new file mode 100644 index 0000000..d08d0ff --- /dev/null +++ b/markdown_timesheet.egg-info/entry_points.txt @@ -0,0 +1,4 @@ + + [console_scripts] + add=app.cli:cli + \ No newline at end of file diff --git a/markdown_timesheet.egg-info/not-zip-safe b/markdown_timesheet.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/markdown_timesheet.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/markdown_timesheet.egg-info/top_level.txt b/markdown_timesheet.egg-info/top_level.txt new file mode 100644 index 0000000..b80f0bd --- /dev/null +++ b/markdown_timesheet.egg-info/top_level.txt @@ -0,0 +1 @@ +app diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..61d9081 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 99 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bfab166 --- /dev/null +++ b/setup.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +"""The setup script.""" + +from setuptools import setup, find_packages + +with open("README.rst") as readme_file: + readme = readme_file.read() + +test_requirements = ["pytest"] + +setup( + author="Zev Averbach", + author_email="zev@averba.ch", + classifiers=[ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: MIT License", + "Natural Language :: English", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], + description="A quick way to add up time entries in a markdown document.", + license="MIT license", + long_description=readme, + include_package_data=True, + keywords="markdown timesheet", + name="markdown_timesheet", + packages=find_packages(include=["app"]), + test_suite="tests", + tests_require=test_requirements, + entry_points=""" + [console_scripts] + add=app.cli:cli + """, + url="https://github.com/zevaverbach/markdown_timesheet", + version="0.1.7", + zip_safe=False, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_add.py b/tests/test_add.py new file mode 100644 index 0000000..2e1020f --- /dev/null +++ b/tests/test_add.py @@ -0,0 +1,71 @@ +from datetime import time + +from pytest import fixture + +from app.add import ( + split_time_range_string, + add_up_timesheet, + find_time_ranges, + parse_time, + subtract_times, +) +from config import SAMPLE_TIMESHEET_PATH + + +@fixture +def timesheet() -> str: + with open(SAMPLE_TIMESHEET_PATH) as fin: + return fin.read() + + +@fixture +def string_with_no_times(): + return "## no times here 123" + + +@fixture +def time_range_strings(): + return ["9:30-12:30", "8:30-7:30"] + + +@fixture +def time_ranges(): + return [["9:30", "12:30"], ["8:30", "7:30"]] + + +@fixture +def times(): + return [(time(9, 30), time(12, 30)), (time(8, 30), time(7, 30))] + + +@fixture +def string_with_times(): + return "## 9:30-12:30, 8:30-7:30 " + + +def test_add_up_timesheet(timesheet): + assert add_up_timesheet(timesheet) == 430 + + +def test_find_time_ranges_none(string_with_no_times): + assert find_time_ranges(string_with_no_times) == [] + + +def test_find_time_ranges(string_with_times, time_range_strings): + assert find_time_ranges(string_with_times) == time_range_strings + + +def test_split_time_range_string(time_range_strings, time_ranges): + for time_range_string, time_range_tuple in zip(time_range_strings, time_ranges): + assert split_time_range_string(time_range_string) == time_range_tuple + + +def test_parse_time(time_ranges, times): + for time_range_tuple, time_tuple in zip(time_ranges, times): + for time_string, time_ in zip(time_range_tuple, time_tuple): + assert parse_time(time_string) == time_ + + +def test_subtract_times(times): + assert subtract_times(*times[0]) == 180 + assert subtract_times(*times[1]) == 1380 diff --git a/tests/timesheet.md b/tests/timesheet.md new file mode 100644 index 0000000..a2b8f70 --- /dev/null +++ b/tests/timesheet.md @@ -0,0 +1,50 @@ +# 30 Aug - 1 Sep 2019 + +## 6.5 hours total +invoiced on 2 Sep 2019 (#6766) + +Gathering requirements, defining scope of work, producing specification and implementation timeline +documents. + +# 2 Sep 2019 +TOTAL: 430 minutes + +## 9:45-10:15 +Researching and answering Evgeny's question about Mailwizz, SES, and email metrics reporting. + +## 10:50-12:54 +Step 3: Making a Jinja template for the newsletter. + +## 12:54-13:17, 14:04-14:12 +Step 4: Test sending the newsletter. + +## 14:12-14:19 +Project scaffolding, installing dependencies, setting up remote git repository. + +## 14:19-15:05 +More step 4: + - writing tests for the render function + - setting up data for test using sample newsletter + - make sure special characters like "&" are escaped (they are in the instantiation of env in + render.py) + +## 15:05-15:33, 15:42-16:27 +Step 6 and 7: Make dummy allocation list and write a function to send a rendered newsletter. + - making one small media CSV + - making one small allocation CSV + - making buckets on my own account (not Syllabus) for testing + +## 16:38-16:56 + - writing to Evgeny to update about rendering from templates + +## 16:56-17:05 + Step 7 + - uploading small media CSV to bucket + - writing function + +## 17:05-17:44 + Adding a few features to admin page spec, asking clarifying questions. + Writing "status" email. + +## 19:55-20:48 + Writing CSV-getting and -parsing for users, media, and allocation diff --git a/types_.py b/types_.py new file mode 100644 index 0000000..ba3eb32 --- /dev/null +++ b/types_.py @@ -0,0 +1,3 @@ +from typing import NewType + +Minutes = NewType("Minutes", int)