Pythonista: Zitatesammlung

Als ich morgens in der Ringbahn durch den App Store klickte, fielen mir einige Apps auf, die nichts anderes machten, als ein paar Zitate anzuzeigen. Da dachte ich mir, so etwas doch auch mal in Python klöppeln zu können. Gesagt, getan. Zumindest der Anfang ist gemacht.

# -*- coding: utf-8 -*-
from scene import *
from random import randrange, choice
 
class MyScene (Scene):
	def setup(self):
		self._changebackground()
		self._shownewquote()
		
	def _changebackground(self):
		background(randrange(50, 90)/100.0, 
		           randrange(50, 90)/100.0,
		           randrange(50, 90)/100.0)
	
	def _truncatestring(self, s, length=25):
		# Split into quote and author
		s, author = s
		# Truncate quote to fixed length, but
		# keeping word boundaries.
		output = []
		while len(s) > 0:
			if len(s) < length:
				output.append(s)
				break
			elif s[length - 1] == ' 'and len(s) > length:
				output.append(s[:length])
				s = s[length:]
			else:
				adjust = s[length:].find(' ')
				if adjust == -1:
					output.append(s)
					s = ''
				else:
					output.append(s[:length + adjust])
					s = s[length + adjust + 1:]
		# Strip all unnecessary blank spaces
		# from resulting strings.
		output = [line.strip() for line in output]
		# Righta-djust author and append to output
		w = max([len(data) for data in output])
		output.append(author.rjust(w))
		return '\n'.join(output)
		
	def _shownewquote(self):
		text(self._truncatestring(rq.next(), 20), x=self.size.w/2, y=self.size.h/2, font_name='DejaVuSansMono', font_size=12)
	
	def touch_began(self, touch):
		self._changebackground()
		self._shownewquote()
 
class RandomQuotes():
	def __init__(self):
		self.quotes = list(open('quotes.txt'))
		self.currentquote = self._newquote()
 
	def next(self):
		while True:
			next = self._newquote()
			if next != self.currentquote:
				self.currentquote = next
				break
		return self.currentquote
		
	def _newquote(self):
		 return choice(self.quotes).split('#-#')
		
if __name__ == '__main__':
	rq = RandomQuotes()
	run(MyScene())

Das Script nimmt sich aus einer weiteren Textdatei (quotes.txt), die auch auf Gist liegt, einige Zitate* und gibt sie dann aus. Bei jedem Touch auf das Display wird ein neues Zitat angezeigt und die Hintergrundfarbe geändert. Feinschliff ist noch überhaupt nicht angesetzt, dafür musste ich zu früh aus der Bahn aussteigen. Aber es tut immerhin ganz ordentlich.

* Die Zitate selbst stammen von Wikiquote.

Drafts-Rezepte IX: Serverstatus bei Github

Da auch den guten Leuten von github mal die Server wegbrechen, gibt es dort eine kleine API. Es ist nur eine klitzekleine Schnittstelle, aber sie wirft raus, wie es gerade und in letzter Zeit um die Erreichbarkeit von github steht. Also basteln wir uns doch mal ein kleines Script, mit dem wir die Daten abrufen können.

from console import alert
from json import loads
from sys import exit
from urllib import urlopen, quote
import webbrowser

def error_dialog(title, message):
    '''A diaolog box for error messages.'''
    try:
        alert(title, message)
    except KeyboardInterrupt:
        pass
    webbrowser.open('drafts://')
    exit(message)

def formatdate(date):
    '''bring the date into shape'''
    return date[:-1].replace('T', ' ')

def request(method):
    '''a request to github's status API.'''
    api_url_base = 'https://status.github.com/api/{method}'
    try:
        response = urlopen(api_url_base.format(method=method))
    except IOError:
        error_dialog('Connection Error', 'Unable to perform request.')
    if response.getcode() != 200:
        error_dialog('Error', 'Status code: {0} - Message: {1}'.format(response.getcode(), response.read()))
    return loads(response.read())

def github_status():
    '''get information and admin messages for github servers.'''
    status = request('status.json')
    messages = request('messages.json')
    output = '''
GITHUB STATUS
================
{date}: {status}

Last messages:
    '''.format(date=formatdate(status['last_updated']),
               status=status['status'])
    for item in messages:
        output += '\n{date}: {status}\n{comment}\n'.format(date=formatdate(item['created_on']),
                                                             status=item['status'],
                                                             comment=item['body'])
    output += '\nFor further information: https://status.github.com/'
    webbrowser.open('drafts://x-callback-url/create?text={0}'.format(quote(output)))

if __name__ == '__main__':
    github_status()

Gist

Die Action in Drafts ist auch keine Neuerung mehr.

pythonista://githubstatus?action=run

Import-Link

Seitdem Drafts auch Actions ausführt, wenn ein Draft keine Zeichen enthält, muss auch nur die Action gefeuert werden. Daraufhin rödelt Pythonista etwas rum, wirft dann aber das Ergebnis als Text in einen neuen Draft aus. Darin stehen dann in knapper Form die Informationen.

Gistacular: Ein mobiles Zuhause für Gist

Was für eine prächtige Ergänzung das kleine Gistacular zu iOctocat ist. Zwar kann ich Gists auch dort ansehen, aber nicht bearbeiten. Gistacular schließt diese Lücke auf einfache Weise.

Die App ist ein stromlinienförmiger Client für Gists. Alles ist übersichtlich angeordnet. Ich kann Favoriten verwalten, Skripte anderer Leute durchforsten, neben den eigenen Skripten natürlich. Der Texteditor ist spartanisch, aber ausreichend. Aber Gistacular zeigt in der Vorschau auch Syntax-Highlighting.

Ich sehe langsam, dass ich meine Desktoprechner nur noch für wirklich umfassende Schreibarbeiten oder eben Spiele brauchen werde. Alles andere kann ich immer besser auch von mobilen Geräten erledigen.

Übrigens, der Quellcode von Gistacular ist freundlicherweise auch auf Github.

Bilder: iTunes

Drafts-Rezepte VII: Postleitzahlen mit Zippopotamus II

Der zweite Teil zu Postleitzahlen mit Zippopotamus geht in die entgegengesetzte Richtung. Statt nun also eine Postleitzahl zu ermitteln, geht es nun um die Städte oder Stadtteile hinter eine Postleitzahl.

Das Script ist wieder genauso einfach wie das erste. In Drafts muss eine neue Notiz nur die folgende Syntax einhalten:

[PLZ],[LÄNDERKÜRZEL]

Beispielsweise würde die folgende Eingabe der berühmten Postleitzahl eine Markdown-Liste in Drafts öffnen.

90210 , us

Auch hier kommt ein kleiner Haken ins Spiel, auch wenn er nicht ganz so lästig ist. Denn die API von Zippopotamus erlaubt es derzeit nicht, das Land nicht anzugeben. Unter Umständen müssen wir also mehr Infos angeben, als wir haben. Das Python-Script sieht derzeit so aus:

# -*- coding: utf-8 -*-
from console import alert
from json import loads
from sys import argv, exit
from urllib import urlopen, quote
import webbrowser

def error_dialog(title, message):
    '''
    a diaolog box for error messages.
    '''
    try:
        alert(title, message)
    except KeyboardInterrupt:
        pass
    webbrowser.open('drafts://')
    exit(message)

def handle_data(data):
    '''
    process json response from zippopotamus.
    will return a markdown list of items.
    '''
    city_json = loads(data)
    output = ''
    for item in city_json['places']:
        output += '- Ort: {place}, Region: {state}\n'.format(place=item['place name'], state=item['state'])
    return output

def get_by_postalcode(data):
    '''
    get all possible cities for a postal code in
    the given country.
    '''
    api_url_base = 'http://api.zippopotam.us/{country}/{postcode}'
    try:
        postcode_, country_= [item.strip() for item in data.split(',')]
    except Exception as err:
        error_dialog(str(err.__class__), err.message)

    try:
        response = urlopen(api_url_base.format(country=country_, postcode=postcode_))
    except IOError:
        error_dialog('Connection Error', 'Unable to perform request.')
    if response.getcode() == 200:
        postcode_data = handle_data(response.read())
        webbrowser.open('drafts://x-callback-url/create?text={0}'.format(quote(postcode_data)))
    else:
        error_dialog('Error', 'Status code: {0} - Message: {1}'.format(response.getcode(), response.read()))

if __name__ == '__main__':
    get_by_postalcode(argv[1])

Gist

Die dazugehörige Action für Drafts ist:

pythonista://city-by-postalcode?action=run&argv=[[draft]]

Import-Link

Drafts-Rezepte VI: Postleitzahlen mit Zippopotamus I

Den kleinen Gag kann ich mir nicht verkneifen. Ich mache zu Zippopotamus, einem guten, kleinen Dienst für Postleitzahlen in einer Vielzahl von Ländern, zwei Teile. Damit artet die Kleinteiligkeit aus wie der überbordende Song-Kosmos von Coheed & Cambria.

Das Script ist wieder sehr einfach. In Drafts muss eine neue Notiz nur die folgende Syntax einhalten:

[STADT], [REGIONSKÜRZEL], [LÄNDERKÜRZEL]

Beispielsweise würde die folgende Eingabe die berühmte Postleitzahlen für die Stadt als Markdown-Liste in Drafts öffenen.

beverly hills, ca, us

Leider kommt diese Sache nicht ohne einen kleinen Haken aus. Denn die API von Zippopotamus erlaubt es derzeit nicht, die Region und das Land nicht anzugeben. Unter Umständen müssen wir also mehr Infos angeben, als wir haben. Ich bin dabei, mir dafür eine Lösung zu überlegen. Das Python-Script sieht derzeit so aus:

# -*- coding: utf-8 -*-
from console import alert
from json import loads
from sys import argv, exit
from urllib import urlopen, quote
import webbrowser

def error_dialog(title, message):
    '''
    a diaolog box for error messages.
    '''
    try:
        alert(title, message)
    except KeyboardInterrupt:
        pass
    webbrowser.open('drafts://')
    exit(message)

def handle_data(data):
    '''
    process json response from zippopotamus.
    will return a markdown list of items.
    '''
    city_json = loads(data)
    output = ''
    for item in city_json['places']:
        output += '- City: {place}, Post code: {postcode}\n'.format(place=item['place name'], postcode=item['post code'])
    return output

def get_by_city(data):
    '''
    get all possible post codes for a city in
    the given country.
    '''
    api_url_base = 'http://api.zippopotam.us/{country}/{state}/{city}'
    try:
        city_, state_, country_= [item.strip() for item in data.split(',')]
    except Exception as err:
        error_dialog(str(err.__class__), err.message)

    try:
        response = urlopen(api_url_base.format(country=country_, state=state_, city=city_))
    except IOError:
        error_dialog('Connection Error', 'Unable to perform request.')
    if response.getcode() == 200:
        city_data = handle_data(response.read())
        webbrowser.open('drafts://x-callback-url/create?text={0}'.format(quote(city_data)))
    else:
        error_dialog('Error', 'Status code: {0} - Message: {1}'.format(response.getcode(), response.read()))

if __name__ == '__main__':
    get_by_city(argv[1])

Gist

Die dazugehörige Action für Drafts ist hier:

pythonista://postalcode-by-city?action=run&argv=[[draft]]

Import-Link

Dunkle Seite der Worte: Letterpress

Okay, ich wollte es nicht in mein Review aufnehmen, muss es aber sagen. Das Spiel ist gut, bietet aber anscheinend jede Menge Gelegenheit zum Trollen und Cheaten. Auch wenn ich es nicht gerne zugebe, getreu dem Motto Gelegenheit macht Diebe bringen gerade Wortpuzzle eine schattige Seite in mir hoch. Warum gerade diese Spiele, keine Ahnung? Der Spaß an Letterpress kann schon ordentlich verdorben werden, wenn Gegner in die Trickkiste greifen.

Impliziter Chat

In Letterpress werden Worte gebildet. Zwar gibt es in dem Spiel keine direkte Form der Kommunikation zwischen Kontrahenten. Einfallsreiche Spieler und besonders Trolle können die Spielmechanik selbst für einige Übergriffe nutzen. Die Matrix der Buchstaben gibt oft was her, um dem Gegenüber einen vor den Kopf zu knallen.

Keine drei Partien hatte ich hinter mir, da lief es auf einen deutlichen Sieg hinaus. Es war absehbar, dass mein Gegner nicht mehr Land – oder Buchstabenflächen – gewinnen würde, also verstieg er sich auf einen rüden Abschied. Im vorletzten seiner Züge bildete er aus den Buchstaben FUCKING. Ich hatte noch nicht Lunte gerochen, bereitete das Spielende mit einem weitern Wort vor und fing mir dann einYOU von ihm.

Ich musste lachen. Nicht schlecht. Letztlich ist auch ungewiss, was genau damit gesagt werden sollte. Wer längere Zeit im Netz – besonders in Spielen – verbringt, nimmt so etwas hin. Man trägt es manchmal sogar als Auszeichnung. Andererseits dürfte es anderen nicht gefallen, wenn es ausarten sollte. In diesem Fall bleibt nur, Spielernamen notieren und zukünftig alle zufällig zugelosten Partien beenden.

Simpler Cheat

Der Spaß kann auch durch eine andere Methode gebrochen werden. Bei Worträtseln lässt sich leicht schummeln. Jeder dürfte schnell ein Wörterbuch zur Hand haben oder Suchmaschinen hinreichend bedienen können. So hagelt es dann schon gerne mal reichlich abstrus erscheinende Wörter. Aber ehrlich, es geht noch schlimmer.

Vorher noch eine Warnung: Wer wie in der Folge beschrieben cheatet, versaut nicht nur anderen den Spaß, sondern langfristig auch sich selbst.

Um inLetterpress in kürzester Zeit das Feld abzudecken, braucht es nicht viel:

  1. Als erstes brauchen wir eine Liste mit einem ordentlichen englischen Wortschatz. Nicht schlecht ist beispielsweise das Official 12Dicts Package. 12Dicts ist nicht schlecht, da es das 2of12inf-Wörterbuch enthält. Inklusive Flexionen verfügt es über 81536 Wörter des Englischen. Das reicht für eine ordentliche Rechtschreibkontrolle – wofür es eigentlich gedacht ist – und wird auch für einen Cheat langen.
  2. Wie machen wir den Abgleich zwischen den uns gegebenen Buchstaben aus Letterpress mit dem Wörterbuch? Reguläre Ausdrücke. Die regulären Ausrücke bieten sich eigentlich für jede Art etwas aufwändigerer Textsuche an, die aber keine linguistische Auswertung liefern soll. Die regulären Ausdrücke sind nicht leicht zugänglich, aber einfach genug. Jeder heutige Texteditor sollte eigentlich eine Regex-Suche unterstützen. Damit kann das Wörterbuch auf diejenigen Wörter reduziert werden, die möglich sind.
  3. Mit ein wenig Scripting geht sogar noch mehr. Ich habe noch nicht die Zeit gehabt, aber sollte nicht so schwer sein, ein Script zu schreiben, in das die aktuelle Matrix des Spiels und der Spielstand angegeben wird, um dann nach einem Wörterbuchabgleich das Wort zu spielen, das möglichst viele Punkte bringt. Weitere Aufgaben und Optimierungen sind hiervon auch nicht weit entfernt.

Mal sehen, ich denke nicht, dass ich mein Script einsetzen werden, sofern ich es beendet habe. Es hat mich nur in den Fingern gejuckt, ein praktisches Projekt zu haben, das selbst ich mit meinen geringen Scripting-Mitteln kurzfristig lösen kann. Den Spaß will ich mir und anderen aber nicht wirklich verderben.

Erste Liegeproben in Assembler

Ganz einfach: Mache eine Endlosschleife und tue in der Endlosschleife nichts. Aber sie tut wirklich was, ehrlich. Da geht was, nur nichts. Jetzt wird es fast philosophisch. Macht eine Schleife, die nichts tut, nicht doch etwas? Ich zitiere die Herren Deichkind: „Du schiebst ’n Geistesblitz bei Variable x. Ich komm‘ mit Party Hits und mach‘ die Party fit. Mir doch egal, wenn ich Dir die Kreide klau‘. Ich geh‘ zur Tafel und schreib‘ „Mathe is‘ scheisse“ drauf.“ Wenn alles so einfach wäre, wie der folgende Code, die Welt wäre ein friedlicher Ort. Kein produktiver, aber ein friedlicher. Lacht, meine Faultiere, lacht.

sloth:
nop
j sloth

Ich bin der Endlosschleifen-König. Ich werde auf meine alten Tage noch ein gigantischer Programmierer.

Bildquelle: Christian Mehlführer CC-BY 2.5