text: import argparse
  import datetime as dt
  import json
  import os
  import signal
  import stat
  import string
  import sys
  
  import mr.db
  import mr.net
  import mr.shell as shell
  
  today = dt.date.today() - dt.timedelta(days=2)
  tomorrow = dt.date.today() - dt.timedelta(days=1)
  
  
  class HckrnewsDatabase(mr.db.Database, metaclass=mr.db.DatabaseMeta):
      def exists(self, cursor):
          cursor.execute('select 1 from day')
  
      def create(self, cursor):
          cursor.execute('''
              create table day(
                  id text primary key,
                  last_updated text
              )''')
          cursor.execute('''
              create table article(
                  id integer primary key,
                  link text,
                  desc text,
                  day text,
                  points integer,
                  comments integer
              )''')
          cursor.execute('create index article_day_idx on article(day)')
          cursor.execute('create index day_last_updated_idx on day(last_updated)')
  
  
  class Day:
      def __init__(self, day, last_updated):
          self.day = day
          self.last_updated = last_updated
  
  
  class Article:
      def __init__(self, article_id, link, desc, day, points, comments):
          self.article_id = article_id
          self.link = link
          self.desc = desc
          self.day = day
          self.points = points
          self.comments = comments
  
  
  class HackerNews:
      def __init__(self, db):
          self.db = db
          self.args = self.parse_args()
  
          self.fetcher = mr.net.Fetcher(quiet=self.args.quiet, sleep=2.0)
  
      def insert_day(self, day):
          self.db.execute('insert into day(id, last_updated) values (?, ?)', (day, dt.date.today()))
  
      def update_day(self, day):
          self.db.execute('update day set last_updated=? where id=?', (dt.date.today(), day))
  
      def get_day(self, day):
          return self.db.select_one(
              'select id, last_updated from day where id = ?',
              (day,),
              Day,
          )
  
      def insert_article(self, article_id, link, desc, day, points, comments):
          self.db.execute(
              '''
                  insert into article(id, link, desc, day, points, comments)
                      values (?, ?, ?, ?, ?, ?)
              ''',
              (article_id, link, desc, day, points, comments),
          )
  
      def update_article(self, article_id, points, comments):
          self.db.execute('update article set points=?, comments=? where id=?', (points, comments, article_id))
  
      def get_article(self, article_id):
          return self.db.select_one(
              '''
                  select id, link, desc, day, points, comments
                      from article
                      where id = ?
              ''',
              (article_id,),
              Article,
          )
  
      def get_articles(self, date_start, date_end):
          return self.db.select_many(
              '''
                  select id, link, desc, day, points, comments
                      from article
                      where day >= ? and day < ?
              ''',
              (date_start, date_end),
              Article,
          )
  
      def select_days_for_update(self, num_days=10):
          rows = self.db.select_many(
              'select id, last_updated from day where id >= ?',
              (today - dt.timedelta(days=num_days),),
          )
          all_days = set(today - dt.timedelta(days=x) for x in range(num_days))
          skip = set()
          for d in rows:
              day = dt.datetime.strptime(d['id'], '%Y-%m-%d').date()
              last_updated = dt.datetime.strptime(d['last_updated'], '%Y-%m-%d').date()
              if last_updated - day >= dt.timedelta(days=7) or last_updated >= dt.date.today():
                  skip.add(day)
          return sorted(list(all_days - skip))
  
      def update_day_from_hckrnews(self, day):
          url = 'https://hckrnews.com/data/{}.js'.format(day.strftime('%Y%m%d'))
          stories = json.loads(self.fetcher.fetch(url))
  
          if self.get_day(day) is None:
              self.insert_day(day)
          else:
              self.update_day(day)
  
          for a in stories:
              try:
                  int(a['id'])
              except ValueError:
                  continue
  
              if self.get_article(a['id']) is None:
                  self.insert_article(a['id'], a['link'], a['link_text'], day, a['points'], a['comments'])
              else:
                  self.update_article(a['id'], a['points'], a['comments'])
  
      def parse_args(self):
          parser = argparse.ArgumentParser()
          parser.add_argument('action', type=str, help='update, month or full')
          parser.add_argument('param', type=str, nargs='?', help='%Y.%m for month')
          parser.add_argument('--quiet', action='store_true')
          parser.add_argument('--all', action='store_true')
          parser.add_argument('--html', action='store_true')
          args = parser.parse_args()
  
          if args.action != 'update' and args.action != 'full' and args.param is None:
              print('{} requires param'.format(args.action))
              sys.exit(0)
  
          return args
  
      def main(self):
          signal.signal(signal.SIGPIPE, signal.SIG_DFL)
  
          # don't die when stopped, try to finish your job first
          signal.signal(signal.SIGTERM, signal.SIG_IGN)
  
          articles = []
  
          if self.args.action == 'update':
              # hckrnews data starts from 2010.06.09, calc the max number like so:
              # (dt.date.today() - dt.date(2010, 6, 9)).days - 1
              for day in self.select_days_for_update(num_days=1652):
                  try:
                      self.update_day_from_hckrnews(day)
                  except (IOError, ValueError):
                      break
          elif self.args.action == 'full':
              articles = self.get_articles(dt.date(1990, 1, 1), dt.date(3000, 1, 1))
          elif self.args.action == 'month':
              # allow only once in a month, on a second sunday
              now = dt.datetime.now()
              if now.weekday() != 6 or (now.day - 1) // 7 != 1:
                  print('Today is not a second sunday of the month. Bye.')
                  sys.exit(0)
  
              date_from = dt.datetime.strptime(self.args.param, '%Y.%m').date()
              if date_from.month < 12:
                  date_to = dt.datetime(date_from.year, date_from.month + 1, 1).date()
              else:
                  date_to = dt.datetime(date_from.year + 1, 1, 1).date()
              articles = self.get_articles(date_from, date_to)
  
          show_all = self.args.all
          if self.args.html:
              template_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'newsitem.html')
              template = string.Template(open(template_file, 'tr').read())
              with open('/tmp/hckrnews.html', 'tw') as html_file:
                  html_file.write('<body style="margin: 30px;">')
                  for article in sorted(articles, key=lambda x: (x.points or 0, x.comments or 0)):
                      if (show_all and (article.points or 0) >= 10) or (not show_all and (article.points or 0) >= 500):
                          html_file.write(template.substitute(
                              points=article.points or '',
                              description=article.desc[:150],
                              article_link=article.link,
                              hn_link='https://news.ycombinator.com/item?id={}'.format(article.article_id),
                              comments=article.comments,
                          ))
                  html_file.write('</body>')
              os.chmod('/tmp/hckrnews.html', stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
          else:
              for article in sorted(articles, key=lambda x: (x.points or 0, x.comments or 0)):
                  if (show_all and (article.points or 0) >= 10) or (not show_all and (article.points or 0) >= 500):
                      print('{:<6}{:<150}'.format(article.points or '', article.desc[:150]))
                      print('          {}'.format(article.link))
                      print('          https://news.ycombinator.com/item?id={}'.format(article.article_id))
                      print('          comments: {}'.format(article.comments))
  
  
  def main():
      with mr.db.DB('hckrnews') as db:
          HackerNews(db).main()
  
  
  if __name__ == '__main__':
      main()
  