Forked from meunomemauricio/toggl_to_hamster.py
Last active
February 10, 2021 12:35
-
-
Save phdd/d250e9edd4629406bef813e974effe02 to your computer and use it in GitHub Desktop.
Script to Import a CSV file exported from Toggl.com to a Hamster DB
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#! /usr/bin/python | |
"""Import CSV Files from Toggl to a Hamster DB.""" | |
import argparse | |
import codecs | |
import csv | |
import sys | |
from datetime import datetime | |
try: | |
import hamster.client | |
except ImportError: | |
print('Hamster must be Installed!') | |
sys.exit(1) | |
# XXX: Depending on the version, Fact is either on __init__ of the package or | |
# on the stuff module. | |
try: | |
from hamster.lib import Fact | |
except ImportError: | |
try: | |
from hamster.lib.stuff import Fact | |
except ImportError: | |
print('Hamster must be Installed!') | |
sys.exit(1) | |
class UTF8Recoder(object): | |
"""Parse CSV files with Unicode.""" | |
def __init__(self, f, encoding): | |
self.reader = codecs.getreader(encoding)(f) | |
def __iter__(self): | |
return self.reader | |
def next(self): | |
"""Reads and returns the next line.""" | |
return self.reader.next().encode('utf-8') | |
class UnicodeReader(object): | |
"""Parse CSV files with Unicode.""" | |
def __init__(self, f, dialect=csv.excel, encoding='utf-8-sig', **kwds): | |
f = UTF8Recoder(f, encoding) | |
self.reader = csv.reader(f, dialect=dialect, **kwds) | |
def next(self): | |
"""Reads and returns the next line as a Unicode string.""" | |
row = self.reader.next() | |
return [unicode(s, 'utf-8') for s in row] | |
def __iter__(self): | |
return self.reader | |
def read_csv_file(origin): | |
"""Read the CSV File exported from the Toggl interface. | |
The CSV has the fields: User, Email, Client, Project, Task, Description, | |
Billable, Start date, Start time, End date, End time, Duration, Tags and | |
Amount. | |
Those are threated as follow: | |
* The Project field is the Hamster Category; | |
* Description is the Hasmter Activity name (not the Hamster Description); | |
* Start Date and Start Time are concatenated and transformed into a | |
datetime object. Same is done to the End Date/Time; | |
* Fields User, Email, Client, Task, Billable and Amount are ignored; | |
* Tags are Tags :D. | |
After parsing, the function returns a list for each line on the CSV file. | |
Each entry on the list is a dictionary in the format: | |
example_dict = { | |
'category': '<Toggl Project>', | |
'activity': '<Toggl Description>', | |
'start_time': datetime('<Start Date> + <Start Time>'), | |
'end_time': datetime('<End Date> + <End Time>'), | |
'tags': ['<tag1>', '<tag2>'] | |
} | |
""" | |
try: | |
with open(origin, 'rb') as csvfile: | |
reader = UnicodeReader(csvfile, delimiter=',', quotechar='"') | |
parsed_list = list(reader) | |
except IOError as error_msg: | |
print(error_msg) | |
sys.exit(1) | |
# Ignore the first line | |
parsed_list = parsed_list[1:] | |
fact_list = [] | |
for line in parsed_list: | |
category = line[3] | |
activity = line[5] | |
client = line[2] | |
start_time = '{0} {1}'.format(line[7], line[8]) | |
end_time = '{0} {1}'.format(line[9], line[10]) | |
# Create a string from the tags | |
tags = client | |
if tags: | |
tags = tags.lower().split(',') | |
tags = [tag.strip() for tag in tags] | |
fact = { | |
'category': category, | |
'activity': activity, | |
'start_time': datetime.strptime(start_time, '%Y-%m-%d %H:%M:%S'), | |
'end_time': datetime.strptime(end_time, '%Y-%m-%d %H:%M:%S'), | |
'tags': tags | |
} | |
fact_list.append(fact) | |
return fact_list | |
def parse_arguments(): | |
"""Parse command line arguments from the CLI.""" | |
parser = argparse.ArgumentParser( | |
description=__doc__, | |
formatter_class=argparse.RawDescriptionHelpFormatter, | |
) | |
parser.add_argument( | |
'-d', '--dryrun', action='store_true', default=False, | |
help='Just prints the parsed contents of the CSV File.' | |
) | |
parser.add_argument('origin', action='store', help='Origin CSV file.') | |
return parser.parse_args() | |
def add_hamster_fact(hamster_storage, fact_dict): | |
"""Add a Hamster Fact to the DB through the API.""" | |
fact = Fact( | |
activity=fact_dict['activity'], | |
category=fact_dict['category'], | |
tags=fact_dict['tags'], | |
start_time=fact_dict['start_time'], | |
end_time=fact_dict['end_time'], | |
) | |
hamster_storage.add_fact(fact) | |
def main(): | |
"""Main Routine.""" | |
args = parse_arguments() | |
print('Parsing CSV File...') | |
fact_list = read_csv_file(args.origin) | |
# Dry Run | |
if args.dryrun: | |
template = ('{category} | {activity} | {start_time} | ' | |
'{end_time} | {tags}') | |
for fact in fact_list: | |
print(template.format(**fact)) | |
sys.exit(0) | |
# Send facts to Hamster | |
print('Storing Facts on Hamster...') | |
hamster_storage = hamster.client.Storage() | |
for fact in fact_list: | |
add_hamster_fact(hamster_storage, fact) | |
print('Done!') | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment