- a typing style screensaver with program input (e.g. "phosphor" of
xscreensaver
) python2
beautifulsoup4
- (
cowsay
)
- start
quip.py
for multiple quips - start
cowquip.sh
for cowsay style output
check the older revision for Bugzilla Quips
xscreensaver
)python2
beautifulsoup4
cowsay
)quip.py
for multiple quipscowquip.sh
for cowsay style outputcheck the older revision for Bugzilla Quips
#!/bin/bash -e | |
EXCLUDED_ANIMALS='calvin|kiss|kosh|mech-and-cow|pony|unipony' | |
function get_list_of_animals() | |
{ | |
cowsay -l | grep -v '^Cow files in' | tr ' ' '\n' | grep -vE '^('"${EXCLUDED_ANIMALS}"')$' | |
} | |
function get_one_random_animal() | |
{ | |
get_list_of_animals | shuf | head -n 1 | |
} | |
function get_one_random_quip() | |
{ | |
"$(dirname "$0")/quip.py" --count 1 --encode iso-8859-1 | |
} | |
###### | |
# MAIN | |
# | |
# Fetch | |
animal="$(get_one_random_animal)" | |
quip="$(get_one_random_quip)" | |
# Display | |
sleep 5 | |
clear | |
echo | |
cowsay -f "${animal}" -- "${quip}" | iconv -t iso-8859-1 2>&1 |
#!/usr/bin/env python2 | |
# -*- encoding: utf-8 -*- | |
"""quip.py is a useful data source for Phosphor screensaver""" | |
__version__ = '2.0.0' | |
__author__ = 'Andras Tim <[email protected]>' | |
import fcntl | |
import os | |
import pickle | |
import random | |
import sys | |
import urllib2 | |
from bs4 import BeautifulSoup | |
from lxml import etree | |
from optparse import OptionParser | |
JIRA_QUIP_XML_URL = os.getenv('JIRA_URL', 'https://jira.balabit/si/jira.issueviews:issue-xml/DBO-1314/DBO-1314.xml') | |
COUNT_OF_QUIPS_PER_RESULT = 15 | |
COUNT_OF_CHARACTERS_PER_RESULT = 1000 | |
ALL_QUIPS_STORAGE = '/var/tmp/all-quips' | |
LEFT_QUIPS_STORAGE = '/var/tmp/left-quips' | |
class QuipStorage(object): | |
def __init__(self, storage_path): | |
self.__quip_storage_path = '{}.pickle'.format(storage_path) | |
self.__quip_lock_path = '{}.lck'.format(storage_path) | |
self.__lock_fd = None | |
self.__quips = [] | |
self.__dirty = False | |
def __enter__(self): | |
return self.open() | |
def __exit__(self, exc_type, exc_val, exc_tb): | |
self.close() | |
def open(self): | |
self.__hold_lock() | |
self.__load_quips() | |
return self | |
def close(self): | |
if self.__dirty: | |
self.__save_quips() | |
self.__release_lock() | |
@property | |
def quips(self): | |
return list(self.__quips) | |
@quips.setter | |
def quips(self, new_quips): | |
if self.__quips == new_quips: | |
return | |
self.__quips = new_quips | |
self.__dirty = True | |
def __load_quips(self): | |
if not os.path.isfile(self.__quip_storage_path): | |
return | |
with open(self.__quip_storage_path, 'r') as fd: | |
picked_data = fd.read() | |
self.__quips = pickle.loads(picked_data) | |
def __save_quips(self): | |
picked_data = pickle.dumps(self.__quips, -1) | |
with open(self.__quip_storage_path, 'w') as fd: | |
fd.write(picked_data) | |
def __hold_lock(self): | |
self.lock_file = open(self.__quip_lock_path, 'a') | |
fcntl.lockf(self.lock_file.fileno(), fcntl.LOCK_EX) | |
def __release_lock(self): | |
fcntl.lockf(self.lock_file.fileno(), fcntl.LOCK_UN) | |
self.lock_file.close() | |
self.lock_file = None | |
class Jira(object): | |
__XPATH_OF_QUIPS = '//item/comments/comment' | |
def __init__(self): | |
self.__last_fetch_storage = QuipStorage(ALL_QUIPS_STORAGE) | |
self.__opener = urllib2.build_opener() | |
def __fetch_quips_url(self): | |
response = self.__opener.open(JIRA_QUIP_XML_URL) | |
return response.read() | |
def __parse_quips_from_raw_html_data(self, html_text): | |
parser = etree.HTMLParser(encoding='utf-8') | |
html = etree.HTML(html_text, parser=parser) | |
li_nodes = html.xpath(self.__XPATH_OF_QUIPS) | |
quips = map(lambda node: BeautifulSoup(node.text).get_text(), li_nodes) | |
return quips | |
def get_quips(self): | |
quips = [] | |
output.print_status(' * Fetching data... ') | |
try: | |
raw_data = self.__fetch_quips_url() | |
quips = self.__parse_quips_from_raw_html_data(raw_data) | |
output.print_status('Done\n') | |
except urllib2.URLError: | |
output.print_status('Error\n') | |
with self.__last_fetch_storage as storage: | |
if quips: | |
storage.quips = quips | |
else: | |
output.print_status(' * Fallback to last fetch storage\n') | |
quips = storage.quips | |
if not quips: | |
raise Exception('Missing quips; maybe auth problem') | |
return quips | |
class ScreenSaverOutput(object): | |
def __init__(self, encoding=None): | |
self.encoding = encoding | |
def print_encoded_list(self, list_of_lines): | |
text = '\n\n'.join(list_of_lines) | |
print(self.__encode_for_screen_saver(text)) | |
def print_status(self, status): | |
sys.stderr.write(self.__encode_for_screen_saver(status)) | |
sys.stderr.flush() | |
def __encode_for_screen_saver(self, text): | |
if not self.encoding: | |
return text | |
if not self.encoding == 'utf-8': | |
text = self.__replace_long_accents(text) | |
return text.encode(self.encoding, 'ignore') | |
@classmethod | |
def __replace_long_accents(cls, text): | |
return text.replace(u'ő', u'ö').replace(u'Ő', u'Ö').replace(u'ű', u'ü').replace(u'Ű', u'Ü') | |
class LeftQuips(object): | |
def __init__(self): | |
self.__left_quip_storage = QuipStorage(LEFT_QUIPS_STORAGE) | |
self.quip_service = Jira() | |
def get_quips(self, max_count, max_characters): | |
result = [] | |
self.__left_quip_storage.open() | |
quips = self.__left_quip_storage.quips | |
self.__fetch_new_items_if_required(quips, max_count) | |
quip_count = 0 | |
character_count = 0 | |
while (quip_count < max_count) and (character_count + len(quips[quip_count]) < max_characters): | |
result.append(quips[quip_count]) | |
character_count += len(quips[quip_count]) | |
quip_count += 1 | |
self.__left_quip_storage.quips = quips[quip_count:] | |
self.__left_quip_storage.close() | |
return result | |
def __fetch_new_items_if_required(self, quips, requested_item_count): | |
if len(quips) >= requested_item_count: | |
return | |
fetched_quips = self.quip_service.get_quips() | |
while len(quips) < requested_item_count: | |
quips.extend(fetched_quips) | |
def parse_args(): | |
app_name = os.path.basename(sys.argv[0]) | |
parser = OptionParser(prog=app_name, description=__doc__, version='%s: %s' % (app_name, __version__)) | |
parser.add_option('-e', '--encode', action='store', dest='encode', | |
help='e.g. iso-8859-1 (default: %default)') | |
parser.add_option('-c', '--count', action='store', dest='count', default=COUNT_OF_QUIPS_PER_RESULT, | |
help='Max count of printed quips (default: %default)') | |
parser.add_option('-C', '--chars', action='store', dest='chars', default=COUNT_OF_CHARACTERS_PER_RESULT, | |
help='Max characters in output (default: %default)') | |
opts, args = parser.parse_args() | |
if int(opts.count) <= 0: | |
parser.error('Incorrect number for count!') | |
opts.count = int(opts.count) | |
if int(opts.chars) <= 0: | |
parser.error('Incorrect number for chars!') | |
opts.chars = int(opts.chars) | |
return opts | |
def main(): | |
global output | |
encoding = None | |
options = parse_args() | |
if options.encode: | |
encoding = options.encode | |
output = ScreenSaverOutput(encoding) | |
left_quips = LeftQuips() | |
quips = left_quips.get_quips(options.count, options.chars) | |
random.shuffle(quips) | |
output.print_encoded_list(quips) | |
if __name__ == '__main__': | |
main() |