order-confirmation-mail-parser 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # PYTHON_ARGCOMPLETE_OK
  4. import re
  5. import os
  6. import sys
  7. import yaml
  8. import email
  9. import pprint
  10. import random
  11. import locale
  12. import argparse
  13. import datetime
  14. import traceback
  15. import subprocess
  16. import HTMLParser
  17. import argcomplete
  18. import BeautifulSoup
  19. class Order(object):
  20. def __init__(self, platform, order_id, order_date, customer_id = None):
  21. assert type(platform) is unicode
  22. self.platform = platform
  23. assert type(order_id) is unicode
  24. self.order_id = order_id
  25. if type(order_date) is datetime.datetime:
  26. order_date = order_date.date()
  27. assert type(order_date) is datetime.date
  28. self.order_date = order_date
  29. assert customer_id is None or type(customer_id) is unicode
  30. self.customer_id = customer_id
  31. self.items = []
  32. self.discounts = []
  33. def dict_repr(self):
  34. return {k: v for (k, v) in {
  35. 'articles': self.items,
  36. 'customer_id': self.customer_id,
  37. 'discounts': self.discounts,
  38. 'order_date': self.order_date.strftime('%Y-%m-%d'),
  39. 'order_id': self.order_id,
  40. 'platform': self.platform,
  41. }.items() if v is not None}
  42. yaml.SafeDumper.add_representer(Order, lambda dumper, order: dumper.represent_dict(order.dict_repr()))
  43. class Figure(object):
  44. def __init__(self, value, unit):
  45. self.value = value
  46. assert type(unit) is unicode
  47. self.unit = unit
  48. class Distance(Figure):
  49. def __init__(self, value, unit):
  50. assert type(value) is float
  51. super(Distance, self).__init__(value, unit)
  52. def metres(self):
  53. if self.unit == 'km':
  54. return self.value * 1000
  55. else:
  56. raise Exception()
  57. class Sum(object):
  58. def __init__(self, value, currency):
  59. assert type(value) is float
  60. self.value = value
  61. if currency == u'€':
  62. currency = u'EUR'
  63. assert type(currency) is unicode
  64. assert currency in [u'EUR']
  65. self.currency = currency
  66. class Discount(object):
  67. def __init__(
  68. self,
  69. name = None,
  70. amount = None,
  71. ):
  72. assert type(name) is unicode
  73. self.name = name
  74. assert type(amount) is Sum
  75. assert amount.value >= 0
  76. self.amount = amount
  77. def dict_repr(self):
  78. return {
  79. 'name': self.name,
  80. 'value': self.amount.value,
  81. 'value_currency': self.amount.currency,
  82. }
  83. yaml.SafeDumper.add_representer(Discount, lambda dumper, discount: dumper.represent_dict(discount.dict_repr()))
  84. class Item(object):
  85. def __init__(
  86. self,
  87. name = None,
  88. price_brutto = None,
  89. ):
  90. assert type(name) is unicode
  91. self.name = name
  92. assert type(price_brutto) is Sum
  93. self.price_brutto = price_brutto
  94. def dict_repr(self):
  95. return {
  96. 'name': self.name,
  97. 'price_brutto': self.price_brutto.value,
  98. 'price_brutto_currency': self.price_brutto.currency,
  99. }
  100. yaml.SafeDumper.add_representer(Item, lambda dumper, item: dumper.represent_dict(item.dict_repr()))
  101. class Article(Item):
  102. def __init__(
  103. self,
  104. quantity = None,
  105. authors = [],
  106. state = None,
  107. reseller = None,
  108. shipper = None,
  109. **kwargs
  110. ):
  111. super(Article, self).__init__(**kwargs)
  112. assert type(quantity) is int
  113. self.quantity = quantity
  114. assert type(authors) is list
  115. self.authors = authors
  116. assert state is None or type(state) is unicode
  117. self.state = state
  118. assert reseller is None or type(reseller) is unicode
  119. self.reseller = reseller
  120. assert shipper is None or type(shipper) is unicode
  121. self.shipper = shipper
  122. self.delivery_date = None
  123. def dict_repr(self):
  124. attr = Item.dict_repr(self)
  125. attr.update({
  126. 'delivery_date': self.delivery_date,
  127. 'quantity': self.quantity,
  128. 'reseller': self.reseller,
  129. 'shipper': self.shipper,
  130. 'state': self.state,
  131. })
  132. if len(self.authors) > 0:
  133. attr['authors'] = self.authors
  134. return attr
  135. yaml.SafeDumper.add_representer(Article, lambda dumper, article: dumper.represent_dict(article.dict_repr()))
  136. class Transportation(Item):
  137. def __init__(self, departure_point = None, destination_point = None, distance = None, **kwargs):
  138. super(Transportation, self).__init__(**kwargs)
  139. assert type(departure_point) is unicode
  140. self.departure_point = departure_point
  141. assert type(destination_point) is unicode
  142. self.destination_point = destination_point
  143. assert distance is None or type(distance) is Distance
  144. self.distance = distance
  145. def dict_repr(self):
  146. attr = Item.dict_repr(self)
  147. attr.update({
  148. 'departure_point': self.departure_point,
  149. 'destination_point': self.destination_point,
  150. 'distance_metres': self.distance.metres() if self.distance else None,
  151. })
  152. return attr
  153. yaml.SafeDumper.add_representer(Transportation, lambda dumper, transportation: dumper.represent_dict(transportation.dict_repr()))
  154. class TaxiRide(Transportation):
  155. def __init__(self, name = None, driver = None, arrival_time = None, departure_time = None, **kwargs):
  156. if name is None:
  157. name = u'Taxi Ride'
  158. super(TaxiRide, self).__init__(name = name, **kwargs)
  159. assert type(driver) is unicode
  160. self.driver = driver
  161. assert arrival_time is None or type(arrival_time) is datetime.datetime
  162. self.arrival_time = arrival_time
  163. assert departure_time is None or type(departure_time) is datetime.datetime
  164. self.departure_time = departure_time
  165. def dict_repr(self):
  166. attr = Transportation.dict_repr(self)
  167. attr.update({
  168. 'arrival_time': self.arrival_time.strftime('%Y-%m-%d %H:%M') if self.arrival_time else None,
  169. 'departure_time': self.departure_time.strftime('%Y-%m-%d %H:%M') if self.departure_time else None,
  170. 'driver': self.driver,
  171. })
  172. return attr
  173. yaml.SafeDumper.add_representer(TaxiRide, lambda dumper, taxi_ride: dumper.represent_dict(taxi_ride.dict_repr()))
  174. def parse_amazon(msg):
  175. msg_text = msg.get_payload()[0].get_payload(decode = True).decode('utf-8')
  176. if not u'Amazon.de Bestellbestätigung' in msg_text:
  177. raise Exception('no amazon order confirmation')
  178. orders = []
  179. for order_text in re.split(ur'={32,}', msg_text)[1:-1]:
  180. order_id = re.search(r'Bestellnummer #(.+)', order_text).group(1)
  181. order_date_formatted = re.search(ur'Aufgegeben am (.+)', order_text, re.UNICODE).group(1)
  182. locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
  183. order_date = datetime.datetime.strptime(order_date_formatted.encode('utf-8'), '%d. %B %Y')
  184. order = Order(
  185. u'amazon.de',
  186. order_id,
  187. order_date
  188. )
  189. articles_text = order_text.split('Bestellte(r) Artikel:')[1].split('_' * 10)[0].strip()
  190. for article_text in re.split(ur'\n\t*\n', articles_text):
  191. article_match = re.match(
  192. ur' *((?P<quantity>\d+) x )?(?P<name>.*)\n'
  193. + ur'( *von (?P<authors>.*)\n)?'
  194. + ur' *(?P<price_brutto_currency>[A-Z]+) (?P<price_brutto>\d+,\d+)\n'
  195. + ur'( *Zustand: (?P<state>.*)\n)?'
  196. + ur' *Verkauft von: (?P<reseller>.*)'
  197. + ur'(\n *Versand durch (?P<shipper>.*))?',
  198. article_text,
  199. re.MULTILINE | re.UNICODE
  200. )
  201. if article_match is None:
  202. sys.stderr.write(repr(article_text) + '\n')
  203. raise Exception('could not match article')
  204. article = article_match.groupdict()
  205. order.items.append(Article(
  206. name = article['name'],
  207. price_brutto = Sum(
  208. float(article['price_brutto'].replace(',', '.')),
  209. article['price_brutto_currency']
  210. ),
  211. quantity = int(article['quantity']) if article['quantity'] else 1,
  212. authors = article['authors'].split(',') if article['authors'] else [],
  213. state = article['state'],
  214. reseller = article['reseller'],
  215. shipper = article['shipper'],
  216. ))
  217. orders.append(order)
  218. return orders
  219. def parse_oebb(msg):
  220. msg_text = msg.get_payload()[0].get_payload(decode = True).decode('utf8')
  221. # msg_text = re.sub(
  222. # r'<[^>]+>',
  223. # '',
  224. # HTMLParser.HTMLParser().unescape(msg.get_payload(decode = True).decode('utf8'))
  225. # )
  226. order_match = re.search(
  227. ur'Booking code:\s+(?P<order_id>[\d ]+)\s+'
  228. + ur'Customer number:\s+(?P<customer_id>PV\d+)\s+'
  229. + ur'Booking date:\s+(?P<order_date>.* \d{4})\s',
  230. msg_text,
  231. re.MULTILINE | re.UNICODE
  232. )
  233. order_match_groups = order_match.groupdict()
  234. locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  235. order_date = datetime.datetime.strptime(
  236. order_match_groups['order_date'],
  237. '%b %d, %Y'
  238. )
  239. order = Order(
  240. u'oebb',
  241. order_match_groups['order_id'],
  242. order_date,
  243. customer_id = order_match_groups['customer_id'],
  244. )
  245. item_match = re.search(
  246. ur'(?P<price_brutto_currency>.)(?P<price_brutto>\d+\.\d+)'
  247. + ur'[\W\w]+'
  248. + ur'Your Booking\s+'
  249. + ur'(?P<departure_point>.*)\s+>\s+(?P<destination_point>.*)',
  250. msg_text,
  251. re.MULTILINE | re.UNICODE
  252. )
  253. item = item_match.groupdict()
  254. order.items.append(Transportation(
  255. name = u'Train Ticket',
  256. price_brutto = Sum(
  257. float(item['price_brutto']),
  258. item['price_brutto_currency'],
  259. ),
  260. departure_point = item['departure_point'],
  261. destination_point = item['destination_point'],
  262. ))
  263. return [order]
  264. def parse_mytaxi(msg):
  265. if not 'mytaxi' in msg.get_payload()[0].get_payload()[0].get_payload(decode = True):
  266. raise Exception('no mytaxi mail')
  267. pdf_compressed = msg.get_payload()[1].get_payload(decode = True)
  268. pdftk = subprocess.Popen(
  269. ['pdftk - output - uncompress'],
  270. shell = True,
  271. stdin = subprocess.PIPE,
  272. stdout = subprocess.PIPE,
  273. )
  274. pdf_uncompressed = pdftk.communicate(
  275. input = pdf_compressed,
  276. )[0].decode('latin-1')
  277. assert type(pdf_uncompressed) is unicode
  278. order_match = re.search(
  279. ur'Rechnungsnummer:[^\(]+\((?P<order_id>\w+)\)',
  280. pdf_uncompressed,
  281. re.MULTILINE | re.UNICODE
  282. )
  283. order_id = order_match.groupdict()['order_id']
  284. ride_match_groups = re.search(
  285. ur'\(Bruttobetrag\)'
  286. + ur'[^\(]+'
  287. + ur'\((?P<price_brutto>\d+,\d+) (?P<price_brutto_currency>.+)\)'
  288. + ur'[\w\W]+'
  289. + ur'\((?P<driver>[^\(]+)\)'
  290. + ur'[^\(]+'
  291. + ur'\(\d+,\d+ .\)'
  292. + ur'[^\(]+'
  293. + ur'\((?P<name>Taxifahrt)'
  294. + ur'[^\(]+'
  295. + ur'\(von: (?P<departure_point>[^\)]+)'
  296. + ur'[^\(]+'
  297. + ur'\(nach: (?P<destination_point>[^\)]+)'
  298. + ur'[\w\W]+'
  299. + ur'Belegdatum \\\(Leistungszeitpunkt\\\):[^\(]+\((?P<arrival_time>\d\d.\d\d.\d\d \d\d:\d\d)\)',
  300. pdf_uncompressed,
  301. re.MULTILINE | re.UNICODE
  302. ).groupdict()
  303. arrival_time = datetime.datetime.strptime(
  304. ride_match_groups['arrival_time'],
  305. '%d.%m.%y %H:%M'
  306. )
  307. order = Order(
  308. u'mytaxi',
  309. order_id,
  310. arrival_time,
  311. )
  312. locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  313. order.items.append(TaxiRide(
  314. price_brutto = Sum(
  315. float(ride_match_groups['price_brutto'].replace(',', '.')),
  316. # why 0x80 ?
  317. u'EUR' if (ride_match_groups['price_brutto_currency'] == u'\x80')
  318. else ride_match_groups['price_brutto_currency'],
  319. ),
  320. departure_point = ride_match_groups['departure_point'],
  321. destination_point = ride_match_groups['destination_point'],
  322. driver = ride_match_groups['driver'],
  323. arrival_time = arrival_time,
  324. ))
  325. return [order]
  326. def parse_uber(msg):
  327. html = msg.get_payload()[0].get_payload(decode = True)
  328. """ document in html2 has the same structure as the one in html.
  329. only difference is that hyperlink urls in html2 have been
  330. replaced by 'email.uber.com/wf/click?upn=.*' urls.
  331. """
  332. html2 = msg.get_payload()[1].get_payload()[0].get_payload(decode = True)
  333. route_map = msg.get_payload()[1].get_payload()[1].get_payload(decode = True)
  334. doc = BeautifulSoup.BeautifulSoup(
  335. html,
  336. convertEntities = BeautifulSoup.BeautifulSoup.HTML_ENTITIES,
  337. )
  338. # strptime
  339. locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
  340. trip_id = re.search(
  341. ur'[\da-f\-]{36}',
  342. doc.find(text = 'Visit the trip page').parent['href'],
  343. ).group(0)
  344. order = Order(
  345. u'uber',
  346. trip_id,
  347. datetime.datetime.strptime(
  348. doc.find(attrs = {'class': 'date'}).text,
  349. '%B %d, %Y',
  350. ),
  351. )
  352. departure_time_tag = doc.find(attrs = {'class': 'from time'})
  353. departure_time = datetime.datetime.strptime(
  354. departure_time_tag.text,
  355. '%I:%M%p',
  356. ).time()
  357. arrival_time_tag = doc.find(attrs = {'class': 'to time'})
  358. arrival_time = datetime.datetime.strptime(
  359. arrival_time_tag.text,
  360. '%I:%M%p',
  361. ).time()
  362. distance = Distance(
  363. float(doc.find(text = 'kilometers').parent.parent.find(attrs = {'class': 'data'}).text),
  364. u'km',
  365. )
  366. fare = doc.find(attrs = {'class': 'header-price'}).find(attrs = {'class': 'header-fare text-pad'}).text
  367. order.items.append(TaxiRide(
  368. name = doc.find(text = 'CAR').parent.parent.find(attrs = {'class': 'data'}).text + ' Ride',
  369. price_brutto = Sum(float(fare[1:]), fare[0]),
  370. arrival_time = datetime.datetime.combine(order.order_date, arrival_time),
  371. departure_time = datetime.datetime.combine(order.order_date, departure_time),
  372. departure_point = departure_time_tag.parent.find(attrs = {'class': 'address'}).text,
  373. destination_point = arrival_time_tag.parent.find(attrs = {'class': 'address'}).text,
  374. distance = distance,
  375. driver = doc.find(attrs = {'class': 'driver-info'}).text[len('You rode with '):],
  376. ))
  377. return [order]
  378. def parse_yipbee(msg):
  379. text = msg.get_payload()[0].get_payload()[0].get_payload(decode = True).decode('utf-8')
  380. if not u'Vielen Dank für deine Bestellung bei yipbee' in text:
  381. raise Exception('no yipbee confirmation')
  382. order_match_groups = re.search(
  383. ur'[\W\w]+'
  384. + ur'BESTELLUNG: (?P<order_id>\w+) vom (?P<order_time>\d\d.\d\d.\d{4} \d\d:\d\d:\d\d)'
  385. + ur'[\W\w]+'
  386. + ur'GESAMTPREIS\s+'
  387. + ur'(?P<articles_and_discount_text>[\W\w]+)'
  388. + ur'(?P<summary_text>ARTIKEL [\W\w]+)',
  389. text,
  390. re.UNICODE
  391. ).groupdict()
  392. order = Order(
  393. u'yipbee',
  394. order_match_groups['order_id'],
  395. datetime.datetime.strptime(order_match_groups['order_time'], '%d.%m.%Y %H:%M:%S'),
  396. )
  397. for article_match in re.finditer(
  398. ur'(?P<name>[\w\-\.\:,%\(\) ]+ (Klasse \d|[\w\-\. ]+[^\d ]))'
  399. + ur'(?P<total_price>\d+,\d\d) €(?P<quantity>\d)(?P<total_price_2>\d+,\d\d) €',
  400. order_match_groups['articles_and_discount_text'].replace('\n', ' '),
  401. re.UNICODE,
  402. ):
  403. article_match_groups = article_match.groupdict()
  404. total_price = float(article_match_groups['total_price'].replace(',', '.'))
  405. total_price_2 = float(article_match_groups['total_price_2'].replace(',', '.'))
  406. assert abs(total_price - total_price_2) < 0.01, 'expected %f, received %f' % (total_price, total_price_2)
  407. quantity = int(article_match_groups['quantity'])
  408. order.items.append(Article(
  409. name = article_match_groups['name'],
  410. price_brutto = Sum(round(total_price / quantity, 2), u'EUR'),
  411. quantity = quantity,
  412. reseller = u'yipbee',
  413. shipper = u'yipbee',
  414. ))
  415. articles_price = float(text.split('RABATTE')[0].split('ARTIKEL')[-1].strip().split(' ')[0].replace(',', '.'))
  416. assert abs(articles_price - sum([a.price_brutto.value * a.quantity for a in order.items])) < 0.01
  417. discount_tag = BeautifulSoup.BeautifulSoup(
  418. order_match_groups['articles_and_discount_text'],
  419. convertEntities = BeautifulSoup.BeautifulSoup.HTML_ENTITIES,
  420. ).find('tr')
  421. if discount_tag:
  422. name_tag, value_tag = discount_tag.findAll('td', recursive = False)
  423. value, currency = value_tag.text.split(' ')
  424. order.discounts.append(Discount(
  425. name = name_tag.text,
  426. amount = Sum(float(value.replace(',', '.')) * -1, currency),
  427. ))
  428. delivery_price = order_match_groups['summary_text'].split('VERSAND')[1].split('STEUERN')[0].strip()
  429. delivery_price_value, delivery_price_currency = delivery_price.split(' ')
  430. order.items.append(Item(
  431. name = u'Delivery',
  432. price_brutto = Sum(float(delivery_price_value.replace(',', '.')), delivery_price_currency),
  433. ))
  434. return [order]
  435. def parse_yipbee_html(msg):
  436. html = msg.get_payload()[0].get_payload()[1].get_payload(decode = True)
  437. if not 'yipbee' in html:
  438. raise Exception('no yipbee confirmation')
  439. doc = BeautifulSoup.BeautifulSoup(html, convertEntities = BeautifulSoup.BeautifulSoup.HTML_ENTITIES)
  440. content_table = doc.find('table')
  441. order_match_groups = re.search(
  442. ur'Bestellung:(?P<order_id>\w+) vom (?P<order_time>\d\d.\d\d.\d{4} \d\d:\d\d:\d\d)',
  443. content_table.find('table').findAll('tr')[3].text,
  444. re.UNICODE
  445. ).groupdict()
  446. order = Order(
  447. u'yipbee',
  448. order_match_groups['order_id'],
  449. datetime.datetime.strptime(order_match_groups['order_time'], '%d.%m.%Y %H:%M:%S'),
  450. )
  451. articles_table = content_table.find('table').find('tbody').findAll('tr', recursive = False)[4].find('table')
  452. for article_row in articles_table.find('tbody').findAll('tr', recursive = False)[1:]:
  453. article_columns = article_row.findAll('td', recursive = False)
  454. (price, currency) = re.sub(ur'\s+', ' ', article_columns[2].text.replace(u',', u'.')).split(' ')
  455. order.items.append(Article(
  456. name = article_columns[1].text,
  457. price_brutto = Sum(float(price), currency),
  458. quantity = int(article_columns[3].text),
  459. reseller = u'yipbee',
  460. shipper = u'yipbee',
  461. ))
  462. discount_row = content_table.find('table').find('tbody').findAll('tr', recursive = False)[6]
  463. (discount_name, discount_value_with_currency) = [c.text for c in discount_row.findAll('td', recursive = False)]
  464. (discount_value, discount_currency) = discount_value_with_currency.split(' ')
  465. order.discounts.append(Discount(
  466. name = discount_name,
  467. amount = Sum(float(discount_value.replace(',', '.')) * -1, discount_currency)
  468. ))
  469. shipping_costs_table = content_table.find('tbody').findAll('tr', recursive = False)[3].findAll('table')[1]
  470. (shipping_price, shipping_currency) = shipping_costs_table.text.replace(',', '.').split(' ')
  471. order.items.append(Item(
  472. name = u'Delivery',
  473. price_brutto = Sum(float(shipping_price), shipping_currency),
  474. ))
  475. return [order]
  476. def parse(msg):
  477. tracebacks = {}
  478. try:
  479. return parse_amazon(msg)
  480. except:
  481. tracebacks['amazon'] = traceback.format_exc()
  482. try:
  483. return parse_oebb(msg)
  484. except:
  485. tracebacks['oebb'] = traceback.format_exc()
  486. try:
  487. return parse_mytaxi(msg)
  488. except:
  489. tracebacks['mytaxi'] = traceback.format_exc()
  490. try:
  491. return parse_uber(msg)
  492. except:
  493. tracebacks['uber'] = traceback.format_exc()
  494. try:
  495. return parse_yipbee(msg)
  496. except:
  497. tracebacks['yipbee'] = traceback.format_exc()
  498. for parser_name in tracebacks:
  499. sys.stderr.write('%s parser: \n%s\n' % (parser_name, tracebacks[parser_name]))
  500. raise Exception('failed to parse')
  501. def compute(register_path):
  502. msg = email.message_from_string(sys.stdin.read())
  503. orders = parse(msg)
  504. if register_path:
  505. with open(register_path, 'r') as register:
  506. registered_orders = yaml.load(register.read().decode('utf-8'))
  507. if not registered_orders:
  508. registered_orders = {}
  509. for order in orders:
  510. if order.platform not in registered_orders:
  511. registered_orders[order.platform] = {}
  512. if order.order_id in registered_orders[order.platform]:
  513. raise Exception('already registered')
  514. registered_orders[order.platform][order.order_id] = order
  515. with open(register_path, 'w') as register:
  516. register.write(yaml.safe_dump(registered_orders, default_flow_style = False))
  517. else:
  518. print(yaml.safe_dump(orders, default_flow_style = False))
  519. def _init_argparser():
  520. argparser = argparse.ArgumentParser(description = None)
  521. argparser.add_argument('--register', metavar = 'path', dest = 'register_path')
  522. return argparser
  523. def main(argv):
  524. argparser = _init_argparser()
  525. argcomplete.autocomplete(argparser)
  526. args = argparser.parse_args(argv)
  527. compute(**vars(args))
  528. return 0
  529. if __name__ == "__main__":
  530. sys.exit(main(sys.argv[1:]))