first
This commit is contained in:
14
Pipfile
Normal file
14
Pipfile
Normal file
@@ -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"
|
||||
123
Pipfile.lock
generated
Normal file
123
Pipfile.lock
generated
Normal file
@@ -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": {}
|
||||
}
|
||||
1
README.rst
Normal file
1
README.rst
Normal file
@@ -0,0 +1 @@
|
||||
...
|
||||
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
48
app/add.py
Normal file
48
app/add.py
Normal file
@@ -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("-")
|
||||
11
app/cli.py
Normal file
11
app/cli.py
Normal file
@@ -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))
|
||||
19
markdown_timesheet.egg-info/PKG-INFO
Normal file
19
markdown_timesheet.egg-info/PKG-INFO
Normal file
@@ -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
|
||||
12
markdown_timesheet.egg-info/SOURCES.txt
Normal file
12
markdown_timesheet.egg-info/SOURCES.txt
Normal file
@@ -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
|
||||
1
markdown_timesheet.egg-info/dependency_links.txt
Normal file
1
markdown_timesheet.egg-info/dependency_links.txt
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
4
markdown_timesheet.egg-info/entry_points.txt
Normal file
4
markdown_timesheet.egg-info/entry_points.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
[console_scripts]
|
||||
add=app.cli:cli
|
||||
|
||||
1
markdown_timesheet.egg-info/not-zip-safe
Normal file
1
markdown_timesheet.egg-info/not-zip-safe
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
1
markdown_timesheet.egg-info/top_level.txt
Normal file
1
markdown_timesheet.egg-info/top_level.txt
Normal file
@@ -0,0 +1 @@
|
||||
app
|
||||
41
setup.py
Normal file
41
setup.py
Normal file
@@ -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,
|
||||
)
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
71
tests/test_add.py
Normal file
71
tests/test_add.py
Normal file
@@ -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
|
||||
50
tests/timesheet.md
Normal file
50
tests/timesheet.md
Normal file
@@ -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
|
||||
Reference in New Issue
Block a user