Commits

Anonymous committed db4c1b7

Added new 0.5 branch

Comments (0)

Files changed (4)

+#!/usr/bin/env python
+
+import os
+import sys
+import getopt
+import logging
+import datetime
+import pyepisoder.episoder as episoder
+import pyepisoder.plugins as plugins
+
+from pyepisoder.episoder import version
+
+def show_version():
+	print 'This is episoder ' + version
+
+def show_help():
+	print """Usage: episoder [options]
+
+Global options:
+  -h			show this help
+  -c <file>		use configuration from file (default: ~/.episoder)
+  -b			update the database
+  -v			verbose operation
+  -w			very verbose (debug) operation
+  -V			show version information
+
+Options for database update:
+  -d <YYYY-MM-DD>	remove episodes prior to this date (default: yesterday)
+  -i			ignore date (don't remove old episodes)
+
+Options for console output:
+  -d <YYYY-MM-DD>	only show episodes newer than date (default: yesterday)
+  -i			ignore date
+  -n <days>		number of future days to show (default: 2),
+			relative to the date set with -d
+  -s <string>		search database for string (NOT IMPLEMENTED YET)
+
+Note that -i overrules -d and -n
+
+Report episoder bugs on http://episoder.sf.net/"""
+
+def parse_rc(path):
+	datafile = None
+	format = '%airdate %show %seasonx%epnum'
+	dateformat = '%a, %b %d, %Y'
+	agent='episoder/' + version
+
+	if not os.path.exists(path):
+		sys.stderr.write(path + ' does not exist\n')
+		sys.exit(2)
+
+	sources = []
+
+	try:
+		rc = open(path)
+	except Exception, msg:
+		sys.stderr.write("%s\n" % msg)
+		sys.exit(2)
+
+	for line in rc:
+		line = line.strip()
+		if line.startswith('data='):
+			(_, datafile) = line.split('=')
+		elif line.startswith('format='):
+			(_, format) = line.split('=')
+		elif line.startswith('dateformat='):
+			(_, dateformat) = line.split('=')
+		elif line.startswith('agent='):
+			(_, agent) = line.split('=')
+		elif line.startswith('src='):
+			data = {}
+			name = None
+			if ' ' in line:
+				(src, opts) = line.split(' ')
+				if opts.startswith('name='):
+					(_, name) = opts.split('=')
+				data['name'] = name
+			else:
+				src = line
+
+			(_, url) = src.split('=')
+			data['url'] = url
+
+			sources.append(data)
+			logging.debug('new source: %s (%s)' % (url, name))
+
+	rc.close()
+
+	if not datafile:
+		sys.stderr.write('No data file defined in configuration\n')
+		sys.exit(3)
+	if len(sources) < 1:
+		sys.stderr.write('No sources defined in configuration\n')
+		sys.exit(3)
+
+	logging.debug('datafile=' + datafile)
+	logging.debug('format=' + format)
+	logging.debug('dateformat=' + dateformat)
+	logging.debug('agent=' + agent)
+
+	config = {
+		'datafile': datafile,
+		'format': format,
+		'dateformat': dateformat,
+		'sources': sources,
+		'agent': agent
+	}
+
+	logging.info('Loaded rcfile')
+
+	return config
+
+def show_data(options, config):
+	store = episoder.DataStore(config['datafile'])
+	renderer = plugins.all()['output'][0]
+	renderer.render(store, options, config)
+
+def update_data(options, config):
+	store = episoder.DataStore(config['datafile'])
+	store.clear()
+
+	for source in config['sources']:
+		url = source['url']
+		parser = plugins.parser_for(url)
+
+		if not parser:
+			logging.warning('No parser found for %s' % url)
+			continue
+
+		parser.parse(source, store)
+
+	if not options['nodate']:
+		basedate = options['date']
+		store.removeBefore(basedate)
+
+def get_options():
+	rcfile = os.path.join(os.environ["HOME"], '.episoder')
+	loglevel = logging.WARNING
+	daysahead = 2
+	date = datetime.date.today() - datetime.timedelta(1)
+	nodate = False
+	search = ''
+	command = show_data
+
+	try:
+		options, args = getopt.getopt(sys.argv[1:], "c:d:hin:s:vVwb")
+	except getopt.error, msg:
+		print msg
+		print "for help use -h"
+		sys.exit(1)
+
+	for option, argument in options:
+		if option == '-h':
+			show_help()
+			sys.exit(0)
+		elif option == '-c':
+			rcfile = argument
+		elif option == '-v':
+			loglevel = logging.INFO
+		elif option == '-w':
+			loglevel = logging.DEBUG
+		elif option == '-V':
+			show_version()
+			sys.exit(0)
+		elif option == '-n':
+			daysahead = int(argument)
+		elif option == '-d':
+			parts = argument.split('-')
+			date = datetime.date(int(parts[0]), int(parts[1]),
+					int(parts[2]))
+		elif option == '-i':
+			nodate = True
+		elif option == '-s':
+			search = argument
+			print "Search is not implemented yet, sorry."
+			sys.exit(99)
+		elif option == '-b':
+			command = update_data
+
+	return {
+		'rcfile': rcfile,
+		'loglevel': loglevel,
+		'days': daysahead,
+		'date': date,
+		'nodate': nodate,
+		'search': search,
+		'command': command
+	}
+
+def main():
+	options = get_options()
+	logging.basicConfig(level=options['loglevel'])
+	config = parse_rc(options['rcfile'])
+	options['command'](options, config)
+
+if __name__ == "__main__":
+	main()

Empty file added.

pyepisoder/episoder.py

+import sys
+import sqlite3
+import logging
+import datetime
+
+version="0.5.0-RC2+SVN"
+
+class DataStore(object):
+	def __init__(self, path):
+		self.logger = logging.getLogger('DataStore')
+		self.conn = sqlite3.connect(path)
+
+	def clear(self):
+		try:
+			self._initdb()
+		except sqlite3.OperationalError, msg:
+			self.logger.error(msg)
+			self.logger.error('If you have an old episoder data ' +
+				'file, please move it away')
+			sys.stderr.write('ERROR\n')
+			sys.exit(10)
+
+	def _initdb(self):
+		self.conn.execute('DROP TABLE IF EXISTS shows')
+		self.conn.execute('CREATE TABLE shows (show_id INTEGER ' +
+			'PRIMARY KEY, show_name TEXT)')
+
+		self.conn.execute('DROP TABLE IF EXISTS episodes')
+		self.conn.execute('CREATE TABLE episodes (show_id INTEGER,' +
+				' num INTEGER, airdate TEXT, season INTEGER,' +
+				' title TEXT, totalnum INTEGER, prodnum TEXT)')
+
+	def addShow(self, show):
+		result = self.conn.execute('INSERT INTO shows VALUES (NULL, ?)',
+				[show])
+		return result.lastrowid
+
+	def getShows(self):
+		shows = self.conn.execute('SELECT * FROM shows')
+		return shows.fetchall()
+
+	def getEpisodes(self, basedate=datetime.date.today(), n_days=0):
+		episodes = []
+		result = self.conn.execute('SELECT show_id, show_name, title,' +
+			'season, num, airdate, prodnum, totalnum FROM ' +
+			'episodes NATURAL JOIN shows WHERE airdate >= ? AND ' +
+			'airdate <= ? ORDER BY airdate ASC', [basedate,
+				basedate + datetime.timedelta(n_days)])
+
+		shows = []
+		episodes = []
+
+		for item in result.fetchall():
+			show = Show(item[1], item[0])
+			if show in shows:
+				show = shows[shows.index(show)]
+			else:
+				shows.append(show)
+
+			airdate = datetime.datetime.strptime(item[5],"%Y-%m-%d")
+
+			episode = Episode(show, item[2], item[3], item[4],
+				airdate.date(), item[6], item[7])
+			episodes.append(episode)
+
+		return episodes
+
+	def addEpisode(self, show, episode):
+		num = episode['episode']
+		airdate = episode['airdate']
+		season = episode['season']
+		title = episode['title']
+		totalnum = episode['totalepnum']
+		prodnum = episode['prodnum']
+
+		self.conn.execute('INSERT INTO episodes VALUES (?, ?, ?, ?,' +
+				'?, ?, ?)', [show, num, airdate, season, title,
+					totalnum, prodnum])
+
+	def commit(self):
+		self.conn.commit()
+
+	def rollback(self):
+		self.conn.rollback()
+
+	def removeBefore(self, date):
+		self.conn.execute('DELETE FROM episodes WHERE airdate < ?',
+				[date.isoformat()])
+		self.commit()
+
+class Episode(object):
+	def __init__(self, show, title, season, episode, airdate, prodnum,
+			total):
+		self.title = title
+		self.season = season
+		self.episode = int(episode)
+		self.airdate = airdate
+		self.prodnum = str(prodnum)
+		self.total = total
+		self.show = show
+
+	def __str__(self):
+		return "%s %dx%02d: %s" % (self.show.name, self.season,
+				self.episode, self.title)
+
+class Show(object):
+	def __init__(self, name, id=-1):
+		self.name = name
+		self.episodes = []
+
+	def addEpisode(self, episode):
+		self.episodes.append(episode)
+
+	def __str__(self):
+		return 'Show("%s")' % self.name
+
+	def __eq__(self, other):
+		return self.name == other.name

pyepisoder/plugins.py

+import os
+import yaml
+import logging
+import urllib2
+import tempfile
+import datetime
+
+def all():
+	return {
+		'parsing': [ EpguidesParser(), DummyParser() ],
+		'output': [ ConsoleRenderer() ]
+	}
+
+def parser_for(url):
+	parsers = all()['parsing']
+
+	for parser in parsers:
+		if parser.accept(url):
+			return parser
+
+	return None
+
+class DummyParser(object):
+	def __init__(self):
+		self.logger = logging.getLogger('DummyParser')
+
+	def __str__(self):
+		return 'dummy parser'
+
+	def accept(self, url):
+		return False
+
+class EpguidesParser(object):
+	def __init__(self):
+		self.logger = logging.getLogger('EpguidesParser')
+		self.awkfile = '/home/stefan/projects/episoder/trunk/episoder/plugins/episoder_helper_epguides.awk'
+		self.awk = '/usr/bin/awk'
+
+	def __str__(self):
+		return 'epguides.com parser'
+
+	def accept(self, url):
+		return url.startswith('http://www.epguides.com/')
+
+	def parse(self, source, store):
+		self.store = store
+		url = source['url']
+
+		if 'name' in source:
+			name = source['name']
+		else:
+			name = None
+
+		webdata = self._fetchPage(url)
+		yamlfile = self._runAwk(webdata)
+		data = self._readYaml(yamlfile, name)
+		self.store.commit()
+		os.unlink(webdata)
+		os.unlink(yamlfile)
+
+	def _fetchPage(self, url):
+		self.logger.info('Fetching ' + url)
+		headers = { 'User-Agent': 'foo' }
+		request = urllib2.Request(url, None, headers)
+		result = urllib2.urlopen(request)
+		(fd, name) = tempfile.mkstemp()
+		file = os.fdopen(fd, 'w')
+		file.write(result.read())
+		file.close()
+		return name
+
+	def _runAwk(self, webdata):
+		self.logger.info('Parsing data')
+		self.logger.debug('Calling AWK')
+		yamlfile = tempfile.mktemp()
+		logfile = tempfile.mktemp()
+		cmd = '%s -f %s output=%s %s >%s 2>&1' % (self.awk,
+			self.awkfile, yamlfile, webdata, logfile)
+		os.system(cmd)
+
+		file = open(logfile)
+		self.logger.debug(file.read().strip())
+		file.close()
+
+		os.unlink(logfile)
+		return yamlfile
+
+	def _readYaml(self, yamlfile, override_name=None):
+		self.logger.debug('Reading YAML')
+		file = open(yamlfile)
+		data = yaml.load(file.read())
+		file.close()
+
+		show = data[0]
+
+		if not 'title' in show or not show['title']:
+			self.logger.warning('Show has no title, aborting')
+			self.store.rollback()
+			return
+
+		if override_name:
+			self.logger.debug('Overriding show name with %s',
+					override_name)
+			title = override_name
+		else:
+			title = show['title']
+
+		self.logger.debug('Got show "%s"', title)
+		show_id = self.store.addShow(title)
+		self.logger.debug('Added with id %d', show_id)
+
+		if not 'episodes' in show or not show['episodes']:
+			self.logger.warning('Show has no episodes, aborting')
+			self.store.rollback()
+			return
+
+		episodes = show['episodes']
+
+		for episode in episodes:
+			self.logger.debug('Found episode %s' % episode['title'])
+			self.store.addEpisode(show_id, episode)
+
+class ConsoleRenderer(object):
+	DEFAULT='\033[30;0m'
+	RED='\033[31;1m'
+	YELLOW='\033[33;1m'
+	GREEN='\033[32;1m'
+	LIGHTBLUE='\033[36;1m'
+	GRAY=DEFAULT
+
+	def __init__(self):
+		self.logger = logging.getLogger('ConsoleRenderer')
+
+	def _renderEpisode(self, episode, color):
+		string = self.config['format']
+		date = episode.airdate.strftime(self.config['dateformat'])
+		string = string.replace('%airdate', date)
+		string = string.replace('%show', episode.show.name)
+		string = string.replace('%season', str(episode.season))
+		string = string.replace('%epnum', "%02d" % episode.episode)
+		string = string.replace('%eptitle', episode.title)
+		string = string.replace('%totalep', str(episode.total))
+		string = string.replace('%prodnum', str(episode.prodnum))
+		print ("%s%s%s" % (color, string, ConsoleRenderer.DEFAULT))
+
+	def render(self, store, options, config):
+		self.config = config
+		today = datetime.date.today()
+		yesterday = today - datetime.timedelta(1)
+		tomorrow = today + datetime.timedelta(1)
+
+		if options['nodate']:
+			startdate = datetime.date(1873, 1, 1)
+			n_days = 109500 # should be fine until late 21xx :)
+		else:
+			startdate = options['date']
+			n_days = options['days']
+
+		episodes = store.getEpisodes(startdate, n_days)
+
+		for episode in episodes:
+			if episode.airdate == yesterday:
+				self._renderEpisode(episode,
+						ConsoleRenderer.RED)
+			elif episode.airdate == today:
+				self._renderEpisode(episode,
+						ConsoleRenderer.YELLOW)
+			elif episode.airdate == tomorrow:
+				self._renderEpisode(episode,
+						ConsoleRenderer.GREEN)
+			elif episode.airdate > tomorrow:
+				self._renderEpisode(episode,
+						ConsoleRenderer.LIGHTBLUE)
+			else:
+				self._renderEpisode(episode,
+						ConsoleRenderer.DEFAULT)
+
+
+
+	"""
+
+	if [ ! -z "$NODATE" ] && [ ! -z "$SEARCH_TEXT" ]; then
+		echo -ne ${color_gray}
+		grep -i "$SEARCH_TEXT" $EPISODER_DATAFILE | while read line; do
+			DATE=${line:0:10}
+			output=`echo $line | sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})/\`date +\"$DATE_FORMAT\" -d $DATE\`/" | sed -e "s/$SEARCH_TEXT/\\\\\E${color_green}\0\\\\\E${color_gray}/ig"`
+			echo -e $output
+		done
+	else
+		YESTERDAY=`get_date_by_offset -1`
+		TODAY=`get_date_by_offset 0`
+		TOMORROW=`get_date_by_offset 1`
+
+		echo -ne ${color_red}
+		grep "^$YESTERDAY" $EPISODER_DATAFILE | grep -i "$SEARCH_TEXT" | sed s/^/\ / | sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})/`date +"$DATE_FORMAT" -d $YESTERDAY`/"
+
+		echo -ne ${color_yellow}
+		grep "^$TODAY" $EPISODER_DATAFILE | grep -i "$SEARCH_TEXT" | sed 's/.*/>\0</' | sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})/`date +"$DATE_FORMAT" -d $TODAY`/"
+
+		echo -ne ${color_green}	
+		grep "^$TOMORROW" $EPISODER_DATAFILE | grep -i "$SEARCH_TEXT" | sed s/^/\ / | sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})/`date +"$DATE_FORMAT" -d $TOMORROW`/"
+
+		echo -ne ${color_lightblue}
+		for ((day=2; day <= $NUM_DAYS; day++)); do
+			DATE=`get_date_by_offset $day`
+			grep "^$DATE" $EPISODER_DATAFILE | grep -i "$SEARCH_TEXT" | sed s/^/\ / | sed -r "s/([0-9]{4}-[0-9]{2}-[0-9]{2})/`date +"$DATE_FORMAT" -d $DATE`/"
+		done
+
+	fi
+	echo -ne ${color_default}"""
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.