Python – Sebastians Blog https://sgaul.de Neues aus den Softwareminen Sun, 13 Apr 2014 15:23:24 +0000 de-DE hourly 1 https://wordpress.org/?v=6.1.7 https://sgaul.de/wp-content/uploads/2019/02/cropped-sgaul-2-1-32x32.jpg Python – Sebastians Blog https://sgaul.de 32 32 YAML-Manipulation mit Python https://sgaul.de/2014/04/13/yaml-manipulation-mit-python/ Sun, 13 Apr 2014 15:23:24 +0000 https://sgaul.de/?p=2597 YAML-Manipulation mit Python weiterlesen]]> Ich mache es zwanzig mal am Tag: t('admin.users.show.name'), die YAML-Datei öffnen, in denen sich die Übersetzungen befinden, den Pfad absuchen und ggf. fehlende Teile einfügen und zuletzt die Übersetzung hineinschreiben und speichern. Wirklich kreativ ist dabei nur das Formulieren, den Rest würde ich gern automatisieren. Dies sollte jedem Rails-Entwickler eine Hilfe sein.

Mein erster Schritt ist eine simple Kommandozeilen-Applikation, die eine YAML-Datei und einen Pfad liest, den Pfad findet oder erstellt, den aktuellen Wert anzeigt, einen neuen entgegennimmt, einträgt und alles in eine Datei schreibt. Das alles möglichst nah an Vim und Sublime, so dass man das ganze später in ein Plugin überführen kann. Somit bietet sich Python an, schon allein um mal wieder was neues zu nutzen.

YAML parsen und Pfade finden

Für diesen Job kommt PyYAML zum Einsatz. Leider machte mir die wirklich schlechte Dokumentation der Bibliothek zu schaffen, im Endeffekt ist es aber recht einfach:

import yaml

input_file = open(args.input_file, "r")
yaml_doc = yaml.load(input_file)

Die Rückgabe ist ein Dict (oder etwas das sich so verhält). In diesem Dictonary kann man nun den gewünschten Pfad suchen:

def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

Ich suche oder erstelle hier das Vaterobjekt des gesuchten Wertes. Ist ein Zwischenschritt kein Dict, breche ich aus Sicherheitsgründen ab, damit keine einfachen Werte oder aber Listen verloren gehen.

Werte lesen und ändern

Mit dem Vaterelement ist das Lesen und Schreiben recht einfach:

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

Mit diesen Funktionen kann man nun eine einfache CLI-Applikation formulieren:

print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

Hier hat mir vor allem Pythons UTF-8-Handhabung Probleme gemacht, weshalb der Wert aus raw_input hier noch dekodiert werden muss. Keine Ahnung wie das schöner geht, in meinen Augen aber erschreckend dass man sich um sowas noch kümmern muss.

In Datei oder Terminal ausgeben

Für das Dumpen des YAML-Dokuments habe sich für mich folgende Einstellungen bewährt:

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")

Auch hier scheint UTF-8 wieder etwas Unerwartetes zu sein. Aber es funktioniert:

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Das Ergebnis

Mit Argparse konnte ich eine Demoanwendung erstellen, die im nächsten Schritt als Vorlage für ein Plugin für Vim oder Sublime dienen soll:

usage: test.py [-h] input_file search_string [output_file]

positional arguments:
  input_file
  search_string
  output_file

optional arguments:
  -h, --help     show this help message and exit

Das Skript kann mit einer Datei und einer Pfadangabe aufgerufen werden. Es zeigt den aktuellen Wert, erfragt einen neuen und schreibt dies in eine angegebene Datei oder das Terminal.

Offene Probleme

Mit dieser Lösung habe ich vor allem ein Problem: PyYAML liest und schreibt die Datei vollständig. Auch Zeilen, in denen nichts geändert wurde, werden daher an die präferierte PyYAML-Syntax angepasst. So wird beispielsweise

de:
  users:
    edit:
      text: |
        Hier können Sie Ihr Passwort anpassen.
        Lassen Sie die Felder frei, um keine Änderung vorzunehmen.

zu

de:                                                                                                                                                                                                  
  users:                                                                                                                                                                                             
    edit:
      change_password: Passwort ändern
      text: 'Hier können Sie Ihr Passwort anpassen.\nLassen Sie die Felder frei, um keine Änderung vorzunehmen.'

Das ist immer noch gültiges YAML, macht sich im Git-Diff aber nicht so schön.

Das gesamte Skript

#!/usr/bin/env python

import sys
import argparse
import yaml

parser = argparse.ArgumentParser()
parser.add_argument('input_file')
parser.add_argument('search_string')
parser.add_argument('output_file', default=None, nargs="?")
args = parser.parse_args()

input_file = open(args.input_file, "r")
search_string = args.search_string
output_file_path = args.output_file
yaml_doc = yaml.load(input_file)


def find_or_create_parent(yaml_doc, search_string):
  yaml_component = yaml_doc
  for path_component in search_string.split(".")[:-1]:
    if path_component in yaml_component:
      if type(yaml_component[path_component]) is dict:
        yaml_component = yaml_component[path_component]
      else:
        sys.exit("Error: Component {0} of {1} exists but is not an object".format(
          path_component, search_string
        ))
    else:
      yaml_component[path_component] = dict()
      yaml_component = yaml_component[path_component]
  return yaml_component

def current_value(yaml_doc, seach_string):
  parent =  find_or_create_parent(yaml_doc, search_string)
  if search_string.split(".")[-1] in parent:
    return parent[search_string.split(".")[-1]]
  else:
    return None

def set_value(yaml_doc, search_string, value):
  parent = find_or_create_parent(yaml_doc, search_string)
  parent[search_string.split(".")[-1]] = value

def dump(yaml_doc):
  return yaml.dump(yaml_doc, default_flow_style=False, allow_unicode=True, encoding="utf-8")


print "Current value of {0}:".format(search_string)
print current_value(yaml_doc, search_string)
new_value = raw_input("Please enter new value: ").decode(sys.stdin.encoding)
set_value(yaml_doc, search_string, new_value)

if output_file_path is None:
  print ""
  print dump(yaml_doc)
else:
  output_file = open(output_file_path, "wb")
  output_file.write(dump(yaml_doc))
  print "Changes have been written into {0}".format(output_file_path)

Abhängigkeiten

Natürlich muss Python installiert sein. Wenn sich das Skript über ein fehlendes YAML-Modul beschwert (ImportError: No module named yaml), lässt sich das in Ubuntu folgendermaßen nachrüsten:

sudo apt-get install python-yaml
]]>
Youtube für die Kommandozeile https://sgaul.de/2014/03/24/youtube-fuer-die-kommandozeile/ Mon, 24 Mar 2014 12:14:50 +0000 https://sgaul.de/?p=2571 MPS-Youtube ist ein Python-Programm für die Kommandozeile, mit dem man komfortabel Musik und Videos von Youtube abspielen und in lokalen Playlists verwalten kann. Zudem lassen sich Videos oder ihr Audioanteil auf die Festplatte herunterladen.

Installation

Die Installation läuft über PIP, eine Art Paketmanager für Pythonmodule ähnlich Rubygems.

sudo apt-get install python-pip

MPS-Youtube setzt von Haus aus auf Mplayer. Das lässt sich ändern, ich bin jedoch den Weg des geringsten Widerstands gegangen:

sudo apt-get install mplayer

Nun kann das eigentliche Programm mittels PIP installiert werden. Es scheint sich hierbei um eine globale Installation zu handeln, Sudo-Rechte waren bei mir erforderlich:

sudo pip install mps-youtube

Nun kann man die vim-ähnliche Oberfläche mittels mpsyt starten.
mpsyt

Anwendung

Audio und Video abspielen

Wie Vim wird das Tool über eine unten angelegte Kommandozeile gesteuert. Ein Slash löst eine Suche nach einem Video, ein Doppelslash die Suche nach einer Youtube-Playlist aus:

/Suchbegriff nach Einzelvideo
//Suchbegriff nach Playlist

Man erhält eine nummerierte Liste mit Ergebnissen. Videos lassen sich durch Eingabe der Nummer abspielen, Playlists in gleicher Weise öffnen. Zudem lassen sich mehrere Videos nacheinander abspielen, etwa 1-10 oder auch 1,5,10.

Fehlende Videoausgabe

Bei mir ist das Programm nach der Installation als Musikwerkzeug ausgelegt, von Videos wird lediglich die Tonspur ausgegeben. Dies kann man temporär umgehen, indem man den abzuspielenden Videonummern die Optionen -f für Fullscreen bzw. -w für den Fenstermodus übergibt. Will man diese Einstellungen als Standard setzen, kann man dies über einen der entsprechenden Set-Befehle machen:

set show_video 1
set fullscreen 1

Playlists

Statt nur der Nummer kann man bspw. mittels add 1-10 oder add 1-10 [Playlistname] die zehn ersten Suchergebnisse in eine lokale Playlist legen. Diese kann man mit vp („view playlist“) öffnen und Inhalte wie Suchergebnisse abspielen. Mit ls lassen sich Playlists auflisten, mit play [Playlistname oder -ID] lassen sich ganze Playlists abspielen.

Insbesondere an dieser Stelle gibt es recht viele, aber auch intuitive Möglichkeiten mit benannten und temporären Listen zu hantieren. Die Github-Seite des Projekts gibt hier recht gute Einblicke. Dem Programm selbst kann man eine kleine Befehlsübersicht mittels help playlists entlocken.

Jukebox und Raspberry Pi

Das Programm hat mich recht positiv überrascht. Es ist klein, schnell und somit vor allem im Vergleich mit der doch immer aufwändiger werdenden Youtube-Seite eine echte Alternative. Mir kommen vor allem der Einsatz als Jukebox und Youtube-Client für den Raspberry Pi in den Sinn. Wer etwas in dieser Richtung sucht, sollte mal eine Blick darauf werfen.

Gotbletu hat das Programm in einem informativen Video zusammengefasst (englisch).

]]>
Git-Commit-Messages aus Redmine-Issues https://sgaul.de/2014/03/07/git-commit-messages-aus-redmine-issues/ Fri, 07 Mar 2014 17:10:20 +0000 https://sgaul.de/?p=2462 Git-Commit-Messages aus Redmine-Issues weiterlesen]]> Ein nerviges Prozedere ist das Suchen nach Issue-Nummern, wenn ich via Git eine Änderung commiten möchte. Git kann hier helfen und bietet mit seinen Hooks die Möglichkeit eigene Skripts in allen denkbaren Situationen auszulösen. Hierzu zählt auch die Modifikation der Commit-Message-Vorlage, die Git mit Status-Kommentaren füllt:

 
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch new
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       modified:   zeus.json
#


Hier hätte ich gern eine Liste der Issues, an denen ich aktuell arbeite. Hierfür muss man in Redmine die Rest-Authentification aktivieren und kann dann im Git-Projekt unter .git/hooks/prepare-commit-msg den folgenden Hook hinterlegen:

#!/usr/bin/env python

import sys, socket, urllib2, json

url = 'http://myredmine/issues.json?key=abcd&project_id=myproject&assigned_to_id=me'
socket.setdefaulttimeout(3)

try:
	originalfile = open(sys.argv[1], "r")
	original = originalfile.read()
	originalfile.close()
	messagefile = open(sys.argv[1], "w")
	data = json.load(urllib2.urlopen(url))
	messagefile.write('\n\n')
	for issue in data["issues"]:
		messagefile.write(('# Issue #' + str(issue["id"]) + ' ' + issue["subject"] + '\n').encode('utf-8'))
	if not original.splitlines()[0]:
		messagefile.write(original)
	messagefile.close()
except:
	print "Failed to get issues from Redmine."

Leider habe ich keine wirkliche Erfahrung mit Python, aber immerhin macht das Skript seinen Job: Es lädt mittels Redmine-API die mir zugewiesenen, offenen Tickets herunter und zeigt Nummer und Titel als Kommentare an.

# Issue #2 Find a new issue #1
# Issue #1 Microsoft has a majority market share

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch new
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#       modified:   zeus.json
#

Nachtrag

Die Commit-Message sollte nur verändert werden, wenn die erste Zeile noch frei ist. Andernfalls kann es zu Problemen mit anderen Programmen wie Git Cola kommen, so dass die Issue-Nummer vor der Commit-Nachricht eingefügt und als Teil dieser Nachricht angesehen wird.

]]>
Boodler – So schön kann Rauschen sein! https://sgaul.de/2011/06/27/boodler-so-schon-kann-rauschen-sein/ https://sgaul.de/2011/06/27/boodler-so-schon-kann-rauschen-sein/#comments Sun, 26 Jun 2011 22:18:58 +0000 https://sgaul.de/?p=189 Boodler – So schön kann Rauschen sein! weiterlesen]]> Musik hören ist eine schöne Sache. Wenn man sich aber wirklich konzentrieren muss, kann sie auch mal störend sein. In vollkommener Ruhe zu arbeiten ist aber oft nicht möglich (danke an meinen Über-mir-Nachbarn) oder für den einen oder anderen auch nicht wünschenswert. Sanfte Hintergrundgeräusche wie Rauschen, Waldgeräusche oder das Knistern eines Lagerfeuers sind eine sehr angenehme Sache. Boodler ist ein Python-Programm, welches solche Geräusche erzeugen kann. Leider ist es nur für die Kommandozeile verfügbar, aber es gibt auch kleine unabhängige GUIs – jetzt auch eine von mir.

Boodler unter Ubuntu 10.10 und 11.04 installieren

Die Anleitung habe ich für Ubuntu 10.10 und 11.04 getestet. Es würde mich nicht wundern, wenn sie auch für andere Versionen und ähnlich auch für andere Distributionen funktioniert.

Wir brauchen Entwicklerpakete für Python und Pulse:

sudo apt-get install python-dev libpulse-dev

Dann das Paket herunterladen und entpacken. In Boodler-Verzeichnis sollte sich eine Datei setup.py befinden:

python setup.py build
sudo python setup.py install

Voilà, Boodler sollte installiert sein.

Pakete installieren

Boodler selber kann noch nichts, man benötigt Pakete. Hierfür einfach aus der offiziellen Liste eines heraussuchen und so installieren:

boodle-mgr install http://boodler.org/lib/com.goob.noise.1.0.boop

Hat man sich für obiges Paket entschieden, sollte ein

boodler -o pulse com.goob.noise/Brown

ein schönes Rauschen erzeugen.

Eine grafische Oberfläche

Da es viele Pakete gibt ist die Sache über die Kommandozeile umständlich zu bedienen. Auch sind die Namen nur schwer zu merken. Wer viel probieren will, möchte vielleicht eine grafische Oberfläche probieren. Die auf der Seite empfohlene QT-Oberfläche läuft unter Ubuntu leider nicht, da sie zum Starten boodler.py statt boodler nutzt und die Option -o pulse nicht setzt. Die reparierte Variante kann von meiner Git-Hub-Seite heruntergeladen werden.

Die Oberfläche basiert wie schon gesagt auf QT. Dementsprechend muss die Python-QT-Bibliothek installiert sein:

sudo apt-get install python-qt4

Ausblick

Das Programm ist eine schöne Übung für Python und QT, mit beidem habe ich bisher leider kaum zu tun gehabt. Oft fehlt auch einfach eine Idee, was man umsetzen kann. Diese habe ich hier nun gefunden. Zumindest einen Import-Dialog für weitere Pakete kann ich mir sehr gut vorstellen.

]]>
https://sgaul.de/2011/06/27/boodler-so-schon-kann-rauschen-sein/feed/ 4