<![CDATA[Martin Puppe]> 2016-04-03T20:49:13+02:00 https://mpuppe.de/ Octopress <![CDATA[Knobeln mit Clojure]> 2016-04-03T20:46:00+02:00 https://mpuppe.de/blog/2016/04/03/knobeln-mit-clojure Das ZEITmagazin enthält regelmäßig unter der Überschrift “Logelei” ein Logikrätsel. Neulich hatte ich Ausgabe Nummer 14/2016 vor mir liegen. Der vollständige Text ist hier zu finden. Ich fasse die wesentlichen Punkte zusammen. Gesucht wird die Kombination für ein Zahlenschloss. Das Zahlenschoss hat sechs Ziffern. Die gesuchte Zahl muss die folgenden Bedingungen erfüllen:

  1. Die Zahl ist durch 4 teilbar.
  2. Die Zahl ist rückwärts ebenfalls durch 4 teilbar.
  3. Die Zahl ist durch 7 teilbar.
  4. Die Zahl ist nicht durch 11 teilbar.
  5. Die Quersumme der Zahl beträgt 32.
  6. Jede Ziffer kommt in der Zahl entweder doppelt oder gar nicht vor.

Nun habe ich erst versucht das Rätsel nur mit Papier und Stift zu lösen. Leider ist Zahlentheorie nicht meine Stärke und mein Wissen erschöpfte sich im konkreten Fall in dem Fakt, dass es reicht die letzten beiden Ziffern einer Zahl zu betrachten, um zu überprüfen, ob sie durch 4 teilbar ist. Also habe ich stattdessen meinen Laptop zur Hand genommen und eine Clojure-REPL geöffnet. Ich habe mir die nötigen Funktionen definiert und schließlich alles zusammengestöpselt. So war das Rätsel trotz Brute-Force-Ansatz relativ schnell gelöst.

Heute habe ich alles noch mal ordentlich aufgeschrieben.Zusätzlich habe ich noch ein paar Tests geschrieben. Den gesamten Quellcode habe ich auf Github veröffentlicht. Bitte schön!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
(ns zahlenschloss.core
  (:gen-class))

(defn teilbar?
  "Gibt `true` zurück, genau dann wenn `n` durch `x` teilbar ist."
  [x n]
  (= 0 (mod n x)))

(doseq [x [4 7 11]]
  (intern *ns*
          (symbol (str "teilbar" x "?"))
          (partial teilbar? x)))


(defn ziffern
  "Nimmt eine Zahl `n` und gibt die Folge der Ziffern der Zahl zurück."
  [n]
  (loop [n n
         ziffern (list)]
    (if (= n 0)
      ziffern
      (recur (int (/ n 10)) (conj ziffern (mod n 10))))))

; http://stackoverflow.com/a/5058544
(defn- exp [x n]
  "Berechnet x hoch n."
  (reduce * (repeat n x)))

(defn wert
  "Nimmt eine Folge von Ziffern und berechnet ihren Zahlenwert."
  [ziffern]
  (loop [summe 0
         faktor (exp 10 (dec (count ziffern)))
         [ziffer & ziffern] ziffern]
    (if ziffer
      (recur
        (+ summe (* ziffer faktor))
        (/ faktor 10)
        ziffern)
      summe)))

(defn links-auffuellen
  "Nimmt eine Folge von Ziffern und füllt sie, falls nötig, links mit
  Nullen auf, sodass das Ergebnis aus mindestens sechs Ziffern besteht."
  [ziffern]
  (loop [laenge (count ziffern)
         ziffern ziffern]
    (if (>= laenge 6)
      ziffern
      (recur
        (inc laenge)
        (conj ziffern 0)))))

(defn rueckwaerts
  "Nimmt eine Zahl und dreht die Folge ihrer Ziffern um. Dabei wird
  angenommen, dass die Zahl mindestens sechsstellig ist. Das Ergebnis
  ist daher mindestens auch sechsstellig."
  [n]
  (wert (reverse (links-auffuellen (ziffern n)))))

(defn quersumme
  "Berechnet die Quersumme der Zahl `n`."
  [n]
  (apply + (ziffern n)))

(defn zaehle-ziffern
  "Zählt, wie oft die einzelnen Ziffern in der Zahl `n` vorkommen. Es
  wird angenommen, dass `n` mindestens sechsstellig ist."
  [n]
  (reduce
    (fn [m ziffer]
      (assoc m ziffer (inc (get m ziffer 0))))
    {}
    (links-auffuellen (ziffern n))))

(defn doppelt-oder-gar-nicht?
  "Gibt `true` zurück, genau dann wenn jede Ziffer in der Zahl doppelt
  oder gar nicht vorkommt. Es wird angenommen, dass `n` mindestens
  sechsstellig ist."
  [n]
  (if (some #(not= % 2) (vals (zaehle-ziffern n)))
    false
    true))

(defn -main
  [& args]
  (println "Mögliche Lösung(en):")
  (apply
    println
    (->> (range 0 1000000)
         ; Die Zahl ist durch 4 teilbar.
         (filter teilbar4?)
         ; Die Zahl ist rückwärts ebenfalls durch 4 teilbar.
         (filter #(teilbar4? (rueckwaerts %)))
         ; Die Zahl ist durch 7 teilbar.
         (filter teilbar7?)
         ; Die Zahl ist nicht durch 11 teilbar.
         (filter (complement teilbar11?))
         ; Die Quersumme der Zahl beträgt 32.
         (filter #(= 32 (quersumme %)))
         ; Jede Ziffer kommt entweder doppelt oder gar nicht vor.
         (filter doppelt-oder-gar-nicht?)
         ; Wir geben maximal 10 Zahlen aus.
         (take 10))))
]>
<![CDATA[Fundstücke 44/2015]> 2015-11-02T23:04:37+01:00 https://mpuppe.de/blog/2015/11/02/fundstucke-44-slash-2015 Automatic Face Recognition and Surveillance
“We need limitations on how our images can be collected without our knowledge or consent, and on how they can be used. The technologies aren’t going away, and we can’t uninvent these capabilities. But we can ensure that they’re used ethically and responsibly, and not just as a mechanism to increase police and corporate power over us.”

Schaffen wir das?
‘Die Formel “nach Maßgabe der Möglichkeiten” ist also, als verfassungsrechtliche Formel, die Verweisung entweder auf ein intellektuelles Nichts oder auf nichts als die politische Willkür. Und sonst wirklich nichts!’

Hardcore History 56 Kings of Kings
Eine neue Folge von Dan Carlins “Hardcore History”.

Frazey Ford - Done
Neulich bei detektor.fm entdeckt.

]>
<![CDATA[Fundstücke 39/2015]> 2015-09-27T21:54:44+02:00 https://mpuppe.de/blog/2015/09/27/fundstucke-39-slash-2015 How to Write a Git Commit Message
Ein paar gute Hinweise.

What Happens Next Will Amaze You
“State surveillance is driven by fear. And corporate surveillance is driven by money. The two kinds of surveillance are as intimately connected as tango partners. They move in lockstep, eyes rapt, searching each other’s souls. The information they collect is complementary. By defending its own worst practices, each side enables the other. Today I want to talk about the corporate side of this partnership.”

Zitat am Freitag: Was wirklich gegen Erkältung hilft
tl;dr: Ausreichend schlafen beugt Erkältungen vor.

]>
<![CDATA[Fundstücke 38/2015]> 2015-09-20T19:37:42+02:00 https://mpuppe.de/blog/2015/09/20/fundstucke-38-slash-2015 Why Futurism Has a Cultural Blindspot
‘“Futurology is almost always wrong,” the historian Judith Flanders suggested to me, “because it rarely takes into account behavioral changes.” And, she says, we look at the wrong things: “Transport to work, rather than the shape of work; technology itself, rather than how our behavior is changed by the very changes that technology brings.” It turns out that predicting who we will be is harder than predicting what we will be able to do.’

Inside Amazon: Wrestling Big Ideas in a Bruising Workplace
Ich werde mich in Zukunft vermehrt nach Alternativen zu Amazon umsehen.

Alternativlos, Folge 35
“In Alternativlos Folge 35 reden wir über Diskurs- und Debattenkultur und die Facebook-Löschdebatte.”

Aynotchesh Yererfu by The Budos Band

]>
<![CDATA[Fundstücke 37/2015]> 2015-09-15T18:39:48+02:00 https://mpuppe.de/blog/2015/09/15/fundstucke-37-slash-2015 The Inspection Paradox is Everywhere
Viele schöne Beispiele zu einem statistischen Paradoxon.

“Und wenn du einfach unter einem Männernamen schreibst?”
Die Autorin bezieht sich auf einen Artikel, den ich hier vor ein paar Wochen auch schon mal verlinkt hatte.

TSA Master Keys
Unter anderem deswegen sind staatlich vorgeschriebene Backdoors eine sehr, sehr dumme Idee.

The Bottom Of The Well
Stellvertretend für viele spannende Episoden des Podcasts “Planet Money”.

WR470 Nachgefragt: Kurdistan
Holger Klein spricht mit Enno Lenze über dessen jüngste Reise nach Kurdistan.

Caravan

]>
<![CDATA[Fundstücke 35/2015]> 2015-08-31T12:21:17+02:00 https://mpuppe.de/blog/2015/08/31/fundstucke-35-slash-2015 Momo
Der Roman ist 1973 erschienen, also bin ich mit meiner Empfehlung etwas spät dran. Das Buch hat aber nichts von seiner Aktualität verloren, eher im Gegenteil. Man findet das Werk zwar meist in der Kinder- und Jugendbuchabteilung, aber gerade auch für Erwachsene gibt es hier viele Denkanstöße. Was ist Zeit und wie gehen wir damit um?

Momo, Dogen. and the Commodification of Time
“This essay will explore the deep resonances between Ende’s view of time in Momo and the Buddhist perspective on time, particularly as expressed by the Japanese Zen master Dogen (1200 - 1253). These resonances are of more than literary or historical interest: understanding what Ende and Dogen have to say about time gives us important insight into how we experience time today.”

The Cat Empire Live at AB - Ancienne Belgique (Full concert)

]>
<![CDATA[Fundstücke 34/2015]> 2015-08-25T17:05:05+02:00 https://mpuppe.de/blog/2015/08/25/fundstucke-34-slash-2015 Reportage: Drei Brüder aus Syrien in der Trierer Aufnahmeeinrichtung für Asylbegehrende
Ich finde den Artikel aus zwei Gründen bemerkenswert: die Reportage ist ein positives Beispiel für Lokaljournalismus, und hier wird mit Flüchtlingen statt nur über sie gesprochen.

The Architecture of Open Source Applications: ZeroMQ
Dieses Kapitel aus The Architecture of Open Source Applications gibt einen guten Überblick über ZeroMQ. Das war für mich ganz hilfreich, weil ich ZeroMQ eventuell bald für ein Projekt einsetzen werde. Übrigens kann ich die ganze Reihe empfehlen. Ich habe zwar bisher nur einzelne Kapitel gelesen, die waren aber allesamt sehr lehrreich.

Waterboarding für den gemeingefährlichen Irren! Deutsche Journalisten über Claus Weselsky
Erschreckend.

Web Design: The First 100 Years
“The Internet is full of projects big and small whose defining trait is that they came out of nowhere and captured people’s imaginations. It’s also full of awesome cat videos. The key part of this vision is that the Internet succeeds by remaining open and participatory. No one acts as gatekeeper, and it is not just a channel for mindless consumption.” (Weitere Vorträge von Maciej Cegłowski)

Confronting New Madrid
Maciej Cegłowski ist durch die New Madrid Seismic Zone gereist und schreibt darüber.

]>
<![CDATA[Fundstücke 33/2015]> 2015-08-17T22:23:48+02:00 https://mpuppe.de/blog/2015/08/17/fundstucke-33-slash-2015 With Windows 10, Microsoft sells you out
“[…] your interest in how your computer works and Microsoft’s interest are no longer aligned. While you will want your computer to do things quickly and efficiently and unobtrusively, Microsoft will want it to do those things slowly and clunkily and painfully; because every delay, every useless dialog box you have to click through, is another opportunity for them to show you an ad. And while you will want your computer to keep your secrets secret, you won’t be able to trust Microsoft to want the same thing anymore, because suddenly all those secrets are worth money to them. They can use them to match you up even more exquisitely with advertisers, who have become Microsoft’s real customers for Windows.”

Fix Windows 10
Immerhin erlaubt Windows 10 recht genau einzustellen, welche Daten an Microsoft gesendet werden. Hier wird das ausführlich mit Screenshots erklärt.

Documentation at scale: The principles
“1. Acknowledge that brute force doesn’t work. 2. Make documentation a first class citizen. 3. Make documentation executable. 4. Track the intent. 5. Measure it.”

Ein Abgrund von Landesverrat
Bundesrichter Thomas Fischer schreibt in seiner Kolumne über den Fall #landesverrat. Dabei kommt seine Meinung sehr deutlich durch. Auch wenn man diese nicht vollständig teilt, ist der Artikel sehr interessant zu lesen.

Lee Fields and the Expressions - “Faithful Man” (Live at WFUV)

]>
<![CDATA[Fundstücke 32/2015]> 2015-08-09T21:27:24+02:00 https://mpuppe.de/blog/2015/08/09/fundstucke-32-slash-2015 Homme de Plume: What I Learned Sending My Novel Out Under a Male Name
“Total data: George sent out 50 queries, and had his manuscript requested 17 times. He is eight and a half times better than me at writing the same book. Fully a third of the agents who saw his query wanted to see more, where my numbers never did shift from one in 25.”

Beyond PEP 8 – Best practices for beautiful intelligible code - PyCon 2015
Aufschlussreicher Vortrag, der aufzeigt wie man besser verständlichen Python-Code schreiben kann. Die ersten ca. 20 Minuten hätte man zwar durchaus etwas kürzen können, aber danach wird es richtig interessant.

Deap Vally - Baby I Call Hell

]>
<![CDATA[Fundstücke 31/2015]> 2015-08-03T17:25:43+02:00 https://mpuppe.de/blog/2015/08/03/fundstucke-31-slash-2015 Nach mehrwöchiger Pause gibt es nun neue Fundstücke von mir.

Are You Living in a Computer Simulation?
“This paper argues that at least one of the following propositions is true: (1) the human species is very likely to go extinct before reaching a “posthuman” stage; (2) any posthuman civilization is extremely unlikely to run a significant number of simulations of their evolutionary history (or variations thereof); (3) we are almost certainly living in a computer simulation. It follows that the belief that there is a significant chance that we will one day become posthumans who run ancestor-simulations is false, unless we are currently living in a simulation. A number of other consequences of this result are also discussed.”

Encryption’s holy grail is getting closer, one way or another
“The ultimate goal – encryption’s holy grail, some have called it – is something called fully homomorphic encryption, where the entire system works on encrypted data, and returns an encrypted result. The only point in the process where data would be decrypted would be when the user wanted to see the result, and that would presumably happen in the application or client software, not in the database server in the cloud.”

What is the Truck Factor of Popular GitHub Applications? A First Assessment (PDF)
Leider hängt Software oft von einzelnen Entwicklern ab. Was passiert, wenn diesen etwas zustößt? Bei freier Software hat man immerhin noch den Quellcode.

Moving Fast With High Code Quality
In dem Artikel wird beschrieben, wie die Leute bei Quora für gute Code-Qualität sorgen.

So ein Europäer will ich gar nicht sein
“Das Europa 2015 ringt mir keinen Pathos mehr ab. Es steht nur noch für die Herrschaft der Buchhalter, für die Ablehnung demokratischer Instrumente wie Abstimmungen, für das Mobben legitimer Regierungen, das Europa 2015 steht für tausende ersoffene Flüchtlinge im Mittelmeer, Überwachung, Militarisierung, Freihandel zugunsten der globalisierten Konzerne, […]” Ich selbst bin ähnlich enttäuscht von Europa.

Einfach. Gut. Leben.
Ich finde die Idee des Minimalismus sehr ansprechend. Mittlerweile überlege ich mir jede Anschaffung drei mal. Zeug und Konsum machen nicht glücklich.

ident.me: Your Address as a Service
Mit curl ident.me ; echo kann man sich ganz einfach seine öffentliche IP-Adresse anzeigen lassen.

Jon Spencer Blues Explosion - Get Your Pants On (Live on KEXP)

]>
<![CDATA[Fundstücke 28/2015]> 2015-07-05T12:14:29+02:00 https://mpuppe.de/blog/2015/07/05/fundstucke-28-slash-2015 Griechenland

Die jetzige Zuspitzung der Krise in und um Griechenland ist nur ein Symptom viel tiefer liegendender Probleme unseres Wirtschaftssystems. Man könnte vieles zu dem Thema schreiben. Vielleicht mache ich das auch irgendwann. Hier erst mal nur Links, ohne Kommentare.

“Die Politik hat sich ins Gefängnis der Märkte begeben”

Gregor Gysi: Der Prohpet

“Volksbefragung”: EU entsetzt über neuartige Entscheidungsmethode aus Griechenland

“Wenn es ernst wird, muss man lügen”

Sonstiges

Inspeqtor and OSS Products With Mike Perham
Mike Perham hat zwei Open-Source-Projekte gestartet, von denen er mittlerweile ganz gut leben kann. Genau genommen, verdient er sein Geld mit proprietären Zusatzfeatures. In dem Artikel, den ich letzte Woche verlinkt habe, wurde dieses Modell als “Open Core” bezeichnet.

“Kein Aserbaidschaner würde je behaupten, in einer Demokratie zu leben”
Ich wusste bisher sehr wenig über Aserbaidschan. Jetzt weiß ich mehr.

The Future of UI Design? Old-School Text Messages und On conversational UIs
“Conversational UIs” sind anscheinend ein großer Trend in China. Ich bin skeptisch. Künstliche Intelligenz ist eben meistens doch nicht intelligent genug. Aber es schadet nicht über Alternativen nachzudenken. Kennt jemand Beispiele für solche “Apps”, die man hierzulande mal ausprobieren könnte?

The Lone Bellow - Button

]>
<![CDATA[Fundstücke 27/2015]> 2015-06-28T21:20:01+02:00 https://mpuppe.de/blog/2015/06/28/fundstucke-27-slash-2015 Statt gelegentlich Links kommentarlos über Twitter zu verteilen, will ich in Zukunft hier im Blog mehr oder weniger (eher weniger) regelmäßig auf interessante Artikel, Vorträge, Musik etc. verweisen.

The Tradeoff Fallacy (PDF, via TechCrunch)
Es wird ja gerne behauptet, dass Konsumenten gerne ihre Daten abgeben, um im Gegenzug kostenfreie Dienste zu erhalten. Diese Studie belegt, dass eine Mehrheit der Konsumenten keineswegs mit diesem Deal einverstanden ist, sondern sich einfach machtlos fühlt. Meine Meinung: Unternehmen wie Facebook sitzen dank Netzwerkeffekt auf einem Monopol, das sie ausnutzen, um die Konditionen zu diktieren. Natürlich könnte ich auf Whatsapp verzichten und einen anderen Messenger nutzen, aber da sind eben nicht die Leute, die ich erreichen will.

The Internet’s Original Sin
Der Artikel verweist auch auf einen großartigen Vortrag von Maciej Cegłowski. Werbefinanzierte Geschäftsmodelle führen notwendigerweise zu Zentralisierung und Überwachung.

A Constructive Look At TempleOS
Es wäre leicht, TempleOS als eine Kuriosität abzutun. Immerhin behauptet der Programmierer, von Gott beauftragt worden zu sein, ein Betriebssystem zu entwickeln. Aber statt billige Witze zu machen, geht der Autor ohne Vorurteile an die Sache heran. Und er stellt fest, dass TempleOS tatsächliche einige coole Ideen beinhaltet.

The Death Of The Von Neumann Architecture
Beim Lesen des Artikels musste ich direkt an Cory Doctorows Vortrag auf dem 28C3 denken. Es geht um Kontrolle und Macht. Wenn ich einen Computer kaufe, gehört er dann mir, oder dem Hersteller? Wenn ich ein Auto kaufe, gehört es dann mir oder dem Hersteller?

How to Make Money from Open Source Platforms Teil 2 Teil 3 Teil 4
Kann man mit Freier Software Geld verdienen? Vor allem Teil 3 fand ich aufschlussreich.

Artificial intelligence?
“We need to be aware of our prejudices and bigotries, and make sure that we don’t build systems that grant our prejudices the appearance of science. That’s ultimately why AI scares us: we worry that it will be as inhuman as humans all too frequently are. If we are aware of our own flaws, and can honestly admit and discuss them, we’ll be OK.” “We might use AI to hide from our responsibility for our mistakes, but they will most certainly be our mistakes.”

TIS-100 und Door Kickers
Zwei tolle Spiele! Vielleicht muss man Programmierer sein, um mit TIS-100 Spaß zu haben. Andererseit hätte ich selbst niemals gedacht, dass ich mal freiwillig Assembler programmieren würde.

9 Anti-Patterns Every Programmer Should Be Aware Of
Besonders über Punkt 3 (“Analysis Paralysis”) stolpere ich leider gelegentlich.

The World Was Never Closer To Nuclear War Than On Jan. 25, 1995

Endangered Blood: NPR Music Tiny Desk Concert

]>
<![CDATA[Installing Rust nightly builds into your home directory]> 2014-11-26T23:53:52+01:00 https://mpuppe.de/blog/2014/11/26/installing-rust-nightly-builds-into-your-home-directory Currently, the easiest way to get up and running with Rust is to run the following command in your shell:

curl -s https://static.rust-lang.org/rustup.sh | sudo sh

This will install Rust into /usr/local and you usually need root permissions to do that. I had been looking for an alternative for two reasons:

  • On my Macbook, /usr/local is mostly managed by Homebrew and brew doctor complains if it finds libraries that were put there by someone else.
  • I don’t have root permissions at the university computer lab.

Fortunately, installing Rust into $HOME is relatively painless. rustup.sh lets you specify a custom prefix. The above command only has to be slightly tweaked:

curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --prefix=$HOME/.local

Once Rust has been installed, there’s still three things left to do.

  1. Put rustc etc. on your $PATH.
  2. Tell rustc where to find the Rust libraries.
  3. Tell man where to find the manual pages.

The first two points can be accomplished by adding the following to your $HOME/.bashrc or $HOME/.zshrc:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if [ -d $HOME/".local/bin" ] ; then
    export PATH="$HOME/.local/bin:$PATH"
fi

rust_dyld=$HOME/.local/lib/rustlib/x86_64-apple-darwin/lib
if [ ! -d $rust_dyld ] ;
    rust_dyld=$HOME/.local/lib/rustlib/x86_64-unknown-linux-gnu/lib
fi

if [ -d $rust_dyld ] ; then
    if [ -z $DYLD_LIBRARY_PATH ] ; then
        export DYLD_LIBRARY_PATH=$rust_dyld
    else
        export DYLD_LIBRARY_PATH=$rust_dyld:$DYLD_LIBRARY_PATH
    fi
fi

unset rust_dyld

Note: Take a look at lines 5 to 8. You should check whether either of these two directories actually exists. If not, you have to modify those lines accordingly.

If you are using fish, put this into $HOME/.config/fish/config.fish instead:

1
2
3
4
5
6
7
8
9
10
11
12
if test -d $HOME/.local/bin
    set -gx PATH $HOME/.local/bin $PATH
end

set -l rust_dyld $HOME/.local/lib/rustlib/x86_64-apple-darwin/lib
if test ! -d $rust_dyld
    set rust_dyld $HOME/.local/lib/rustlib/x86_64-unknown-linux-gnu/lib
end

if test -d $rust_dyld
    set -gx DYLD_LIBRARY_PATH $rust_dyld $DYLD_LIBRARY_PATH
end

Now we need to tell man where to find the manual pages. Add the following line to $HOME/.manpath:

MANPATH_MAP /Users/martin/.local/bin    /Users/martin/.local/share/man

Finally, start a new terminal session and try the following:

rustc --version
man rustc

If you later want to upgrade to the latest nightly, just rerun rustup.sh (like above):

curl -s https://static.rust-lang.org/rustup.sh | sh -s -- --prefix=$HOME/.local
]>
<![CDATA[Git-Repositories ohne Root-Rechte hosten mit Gitolite]> 2014-05-23T13:04:45+02:00 https://mpuppe.de/blog/2014/05/23/git-repositories-ohne-root-rechte-hosten-mit-gitolite Über die Vorzüge von Git brauche ich an dieser Stelle nicht viele Worte verlieren. Das verteilte Versionkontrollsystem ist bei der Software-Entwicklung ein äußerst nützliches Werkzeug. Ein Server ist dabei nicht unbedingt notwendig, um es zu verwenden. Aber ein zentrales Repository, das immer erreichbar ist, hat durchaus einige Vorteile. In diesem Artikel möchte ich zeigen, wie einfach es ist, für einzelne Benutzer oder für Gruppen Repositories auf einem Server zu hosten. Und das Beste dabei ist: mehr als einen SSH-Zugang mit einfachen Benutzerrechten braucht man dazu nicht!

Im Folgenden gehen wir davon aus, dass ein SSH-Zugang mit dem Benutzernamen “benutzer” auf dem Server “server” vorhanden ist.

Einzelne Benutzer

Möchte man ganz allein an einem Repository arbeiten, so ist die Einrichtung besonders simpel. Es reicht aus, auf dem Server mit git init --bare repo.git1 ein “nacktes” Repository zu erzeugen, also ein Repository das keine Arbeitsverzeichnis enthält. Unter der Annahme, dass der vorhergehende Befehl im Verzeichnis $HOME/git des Servers ausgeführt wurde, kann auf der lokalen Maschine jetzt mittels git clone benutzer@server:git/repo.git ein Klon des Repositories erstellt werden. Mit git push und git pull Änderungen zwischen lokalem und entferntem Repository übermittelt werden.

Gruppen

Nehmen wir nun an, dass wir auch anderen Personen lesenden oder schreibenden Zugriff auf ein oder mehrere Repositories gewähren wollen. Außerdem nehmen wir an, dass wir diesen Personen nur den Zugriff auf die Repositories nicht aber auf unser komplettes Benutzerkonto gestatten wollen. Ohne die notwendigen Rechte, um eigens dafür eine Unix-Gruppe zu erstellen, scheint das Problem nur schwierig zu lösen zu sein. Doch dank gitolite kann man einen anderen Weg gehen. Neben Git muss der Server dazu nur zwei Voraussetzungen erfüllen:

  1. der Server muss SSH-Authentifizierung mittels Schlüsselpaar erlauben, und
  2. es muss ein Perl-Interpreter vorhanden sein.

Beides sollte unter den meisten Unix-Systemen gegeben sein.

Kurz zur Funktionsweise von Gitolite. SSH erlaubt die Authentifizierung mittels Schlüsselpaar. Gitolite nutzt diesen Mechanismus, um am Schlüssel zu erkennen, welcher Gitolite-Benutzer sich einloggt. Gitolite erlaubt dabei aber den Benutzern nur den Zugriff auf die Git-Repositories nicht auf den vollständigen Benutzer-Account.

Gitolite einrichten

Als erstes erstellen wir auf dem lokalen Rechner ein SSH-Schlüsselpaar. Dazu wird das Kommando ssh-keygen verwendet. Die Voreinstellung für den Dateinamen kann man mit Return bestätigen. Es ist ratsam eine gute Passphrase zu verwenden2. Den öffentlichen Teil des soeben erzeugten Schlüsselpaares kopiert man nun auf den Server. Der Name der Datei auf dem Server kann dabei frei gewählt werden, und entspricht dem Namen unter dem Gitolite den Benutzer später kennen wird (hier: “alice”).

1
scp $HOME/.ssh/id_rsa.pub benutzer@server:alice.pub

Nun loggen wir uns auf dem Server ein und stellen mittels echo $PATH sicher, dass der Ordner bin im Homeverzeichnis existiert und Teil des Suchpfads für ausführbare Dateien ist. Falls nicht:

1
2
mkdir $HOME/bin
export PATH=$HOME/bin:$PATH

Damit die Änderung von $PATH auch nach dem Schließen des Terminals oder dem Ausloggen erhalten bleibt, trägt man die zweite Zeile außerdem in die Datei $HOME/.profile oder $HOME/.bashrc ein.

Nun wird Gitolite installiert und initialisiert. Hier im Beispiel wird Gitolite in das Verzeichnis $HOME/src/gitolite installiert.

1
2
3
4
5
6
mkdir $HOME/src
cd $HOME/src
git clone git://github.com/sitaramc/gitolite
gitolite/install -ln
cd $HOME
gitolite setup -pk alice.pub

Falls vorher schon der selbe SSH-Schlüssel zur Anmeldung verwendet wurden, wird nach der Eingabe des letzten Befehls eventuell eine Warnung angezeigt. Der Grund ist, dass der Schlüssel nun doppelt in der Datei $HOME/.ssh/authorized_keys vorhanden ist. Falls dies der Fall ist, sollte der entsprechende alte Eintrag entfernt werden. ACHTUNG: Mit dem Schlüssel ist dann aber keine Anmeldung mehr mit vollem Shell-Zugriff möglich, es sei denn der folgende Schritt wird ausgeführt.

Einem Gitolite-Nutzer Shell-Zugriff erlauben

Dem Gitolite-Nutzer (hier “alice”) dem der eigentliche SSH-Zugang gehört, sollte man vollen Zugriff auf den (Unix-)Nutzeraccount gewähren. Dazu wird zunächst der Befehl echo alice > $HOME/.gitolite.shell-users ausgeführt. Dann wird an zwei Stellen die Datei $HOME/.gitoliterc bearbeitet. Die erste Stelle ist die folgende Zeile:

1
# SHELL_USERS_LIST        =>  "$ENV{HOME}/.gitolite.shell-users",

Hier wird das Zeichen # entfernt. Die zweite Stelle sieht so aus:

1
2
# give some users direct shell access
# 'Shell',

Auch hier wird in der zweiten Zeile das # entfernt. Anschließend muss noch der Befehl gitolite compile; gitolite trigger POST_COMPILE ausgeführt werden.

Administration von Gitolite

Damit ist die Einrichtung von Gitolite abgeschlossen. Nun stellt sich die Frage, wie man weitere Benuter und Repositories hinzufügt. In Gitolite wird die gesamte Administration über ein dafür bei der Initialisierung erzeugtes Repository abgewickelt. Auf dem lokalen Rechner wird dieses daher erst mal geklont:

1
git clone benutzer@server:gitolite-admin.git

In diesem Repository befinden sich zwei Unterverzeichnisse, conf und keydir. Benutzer kann man nun einfach hinzufügen, indem man deren öffentliche Schlüssel nach keydir kopiert und sie nach dem Schema name.pub benennt.

Um Repositories hinzuzufügen muss die Datei conf/gitolite.conf bearbeitet werden. Ein Beispieleintrag sähe folgendermaßen aus:

gitolite-admin/conf/gitolite.conf
1
2
3
4
repo foo
    RW+     =   alice
    R       =   bob
    RW      =   eve

In diesem Beispiel hat Alice darf lesen, schreiben und Rewinds durchführen; Bob darf nur lesen; Eve darf lesen und schreiben, aber keine Rewinds durchführen.

Nachdem Änderungen durchgeführt wurden, müssen diese committet werden und auf den Server gepusht werden. Erst dann werden sie wirksam und hier im Beispiel wird das Repository foo erstellt. Anschließend kann etwa Eve mit

1
git clone benutzer@server:foo.git

das Repository klonen. Wobei der Unix-Benuter “benutzer” für alle Gitolite-Benutzer der selbe ist.

  1. Die Endung “.git” ist dabei nicht notwendig, aber per Konvention werden so nackte Repositories kenntlich gemacht, um sie von normalen Repositories zu unterscheiden.

  2. Eine gute Methode, um sichere Passphrasen zu erzeugen, die dennoch leicht zu merken sind, ist Diceware.

]>
<![CDATA[BitTorrent Sync auf QNAP-NAS-Geräten]> 2013-05-21T17:31:00+02:00 https://mpuppe.de/blog/2013/05/21/bittorrent-sync-auf-qnap-nas-geraten Dropbox (Referral-Link) ist an sich eine schöne Sache. Einfach Dateien in einen Ordner werfen, und auf jedem Gerät, das man mit seinem Dropbox-Account verbindet, wird dieser Ordner synchronisiert. Das erlaubt mir beispielsweise, überall auf die Dateien zuzugreifen, die ich für die Uni brauche. Aber Dropbox hat auch Nachteile. Es ist mit einem Dollar pro Jahr und Gigabyte recht teuer. Außerdem hat Dropbox jederzeit Zugriff, auf die Daten, die man ihnen anvertraut.

Und hier kommt BitTorrent Sync ins Spiel. Das Programm erlaubt es, beliebig große Ordner zwsichen beliebig vielen Rechnern verschlüsselt über das BitTorrent-Protokoll zu synchronisieren. BitTorrent Sync ist zwar erst in der Alpha-Phase, läuft aber bereits recht stabil.

Die Synchronisierung zwischen zwei Rechnern funktioniert natürlich nur, wenn beide Rechner gleichzeitig eingeschaltet sind. Um eine mit Dropbox vergleichbare Funktionalität zu bekommen, braucht man also einen Server, der rund um die Uhr eingeschaltet ist, oder zumindest immer dann, wenn einer der anderen Rechner läuft. Dafür kommt beispielsweise ein NAS-Server in Frage, wie sie etwa von QNAP hergestellt werden.

Und damit kommen wir zum eigentlichen Inhalt dieses Artikels. Ich hatte am Pfingst-Wochenende etwas Zeit zum Experimentieren und habe BitTorrent Sync auf meinem QNAP-NAS zum Laufen bekommen. Eine Anleitung und die benötigten Skripte sind unter https://github.com/puppe/btsync-qpkg zu finden.

]>
<![CDATA[Unter Linux einen Proxy verwenden]> 2013-04-15T21:11:00+02:00 https://mpuppe.de/blog/2013/04/15/unter-linux-einen-proxy-verwenden Manchmal ist man in Netzwerken unterwegs, deren Firewall erst mal jeglichen Netzwerkverkehr blockt. Um den Benutzern dennoch zu ermöglichen, zumindest auf das Web zuzugreifen, wird dann meist ein Proxy-Server zur Verfügung gestellt, der zwischen den Benutzern und dem eigentlichen Ziel der Anfragen vermittelt.

Nehmen wir einmal an, dass der Proxy-Server unter der IP-Adresse 192.168.0.42 erreichbar ist und auf Port 8080 lauscht. Dann muss in die Datei /etc/profile folgendes eingetragen werden:

1
2
3
4
5
6
export http_proxy=http://192.168.0.42:8080
export https_proxy=http://192.168.0.42:8080
export ftp_proxy=http://192.168.0.42:8080
export HTTP_PROXY=$http_proxy
export HTTPS_PROXY=$https_proxy
export FTP_PROXY=$ftp_proxy

Was aber, wenn man für manche Anfragen explizit keinen Proxy-Server verwenden will, z.B. weil man auf Dienste zugreifen will, die auf dem eigenen Rechner oder innerhalb des lokalen Netzwerkes laufen? Dazu dient die Umgebungsvariable no_proxy. Möchte man beispielsweise für Anfragen an localhost oder die IP-Adresse 192.168.0.10 keinen Proxy verwenden, so sollte der Datei /etc/profile folgende Zeile hinzugefügt werden:

1
export no_proxy=localhost,192.168.0.10
]>
<![CDATA[Hadoop unter Debian Squeeze installieren]> 2013-03-15T12:09:00+01:00 https://mpuppe.de/blog/2013/03/15/hadoop-unter-debian-squeeze-installieren Im folgenden Artikel beschreibe ich, wie Hadoop unter Debian Squeeze im sogenannten pseudo-verteilten Modus installiert werden kann. Den Text habe ich im Rahmen meiner Tätigkeit als studentische Hilfskraft beim Leibniz-Zentrum für Psychologische Information und Dokumentation (ZPID) für ein internes Wiki verfasst. Da ich denke, dass diese Anleitung auch für andere nützlich sein könnte, veröffentliche ich sie auch hier im Blog.


Die wichtigsten Bestandteile von Hadoop sind:

  • Hadoop Distributed File System (im Folgenden HDFS)
  • Hadoop MapReduce (im Folgenden HMR)

Beide folgen einer Master/Slave-Architektur. Der Master-Dienst heißt bei HDFS NameNode, der Slave-Dienst DataNode. Bei HMR ist der sogenannte JobTracker der Master und die TaskTracker sind die Slaves. Typischerweise besteht ein Hadoop-Cluster aus mindestens drei Arten von Maschinen:

  • Ein dedizierter NameNode
  • Ein dedizierter JobTracker
  • Mehrere Maschinen, auf denen sowohl der DataNode- als auch der TaskTracker-Dienst läuft

Als Einstieg in den Betrieb eines Hadoop-Clusters, und um Programme zu testen, die das HMR-Framework verwenden, bietet es sich an, erst einmal alle Dienste auf einer einzigen Maschine laufen zu lassen. Dieser Operationsmodus wird als pseudo-verteilt bezeichnet.

Wir gehen von folgenden Voraussetzungen aus:

  • Betriebssystem: Debian 6 Squeeze (sollte aber auch für die meisten anderen Debian-basierten Distributionen funktionieren)
  • Installierte Java Runtime Environment 6 (hier das Paket openjdk-6-jre-headless)

Installation

Download-Links sind unter http://hadoop.apache.org/releases.html zu finden. Wir laden z.B. die aktuelle stabile Version für x86-Systeme und deren Signatur von einem Spiegel-Server herunter:

1
2
wget http://mirror.synyx.de/apache/hadoop/common/hadoop-1.0.4/hadoop_1.0.4-1_i386.deb
wget http://mirror.synyx.de/apache/hadoop/common/hadoop-1.0.4/hadoop_1.0.4-1_i386.deb.asc

Außerdem laden wir die Datei KEYS von einem Server der Apache Foundation und importieren die Schlüssel in den GPG-Schlüsselring. Anschließend verifizieren wir die Signatur.

1
2
3
wget http://www.eu.apache.org/dist/hadoop/common/KEYS
gpg --import KEYS
gpg --verify hadoop_1.0.4-1_i386.deb.asc

Schließlich installieren wir das Paket:

1
sudo dpkg -i hadoop_1.0.4-1_i386.deb

Bei der Installation werden automatisch die Gruppe hadoop und die Benutzer hdfs und mapred angelegt.

Konfiguration

Zunächst müssen unter /etc/hadoop die Dateien core-site.xml, hdfs-site.xml und mapred-site.xml mit folgendem Inhalt angelegt werden:

core-site.xml
1
2
3
4
5
6
<configuration>
     <property>
         <name>fs.default.name</name>
         <value>hdfs://localhost:9000</value>
     </property>
</configuration>
hdfs-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
    <property>
        <name>dfs.replication</name>
        <value>1</value>
    </property>
    <property>
        <name>dfs.name.dir</name>
        <value>/var/local/lib/hadoop-dfs/name</value>
    </property>
    <property>
        <name>dfs.data.dir</name>
        <value>/var/local/lib/hadoop-dfs/data</value>
    </property>
</configuration>
mapred-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<configuration>
    <property>
        <name>mapred.job.tracker</name>
        <value>localhost:9001</value>
    </property>
    <property>
        <name>mapred.system.dir</name>
        <value>/hadoop/mapred/system</value>
    </property>
    <property>
        <name>mapreduce.jobtracker.staging.root.dir</name>
        <value>/user</value>
    </property>
</configuration>

Eine Übersicht über alle Konfigurationsoptionen und deren Standardwerte findet sich unter:

Zuletzt müssen wir noch sicherstellen, dass die Umgebungsvariable JAVA_HOME korrekt gesetzt wird. Dazu bearbeiten wir die Datei /etc/hadoop/hadoop-env.sh bearbeiten. Wir ersetzen die Zeile

1
export JAVA_HOME=/usr/lib/jvm/java-6-sun

durch

1
export JAVA_HOME=/usr/lib/jvm/java-6-openjdk

Wichtig! Damit die letzte Änderung wirksam wird, müssen wir uns aus- und wieder einloggen. Mittels echo $JAVA_HOME kann überprüft werden, ob die Variable richtig gesetzt ist.

Erstes Starten der Dienste

Wir werden zuerst das HDFS und dann HMR starten.

HDFS starten

Bevor wir HDFS starten, müssen wir die lokalen Ordner erstellen, in denen die Daten des HDFS abgelegt werden. Es handelt sich dabei, um die Ordner die zuvor in der Konfigurationsdatei hdfs-site.xml festgelegt wurden. Anschließend wirds verteilte Dateisystem formatiert. Einige der Operationen müssen als Benutzer hdfs ausgeführt werden. In diesen Fällen steht am Beginn des Befehls sudo -u hdfs.

1
2
3
4
sudo mkdir -p /var/lib/local/hadoop-dfs
sudo chown hdfs:hadoop /var/lib/local/hadoop-dfs
sudo -u hdfs mkdir /var/lib/local/hadoop-dfs/name /var/lib/local/hadoop-dfs/data
sudo -u hdfs hadoop namenode -format

Nun können wir den NameNode-Dienst und den DataNode-Dienst starten.

1
2
sudo service hadoop-namenode start
sudo service hadoop-datanode start

Nun sollte es möglich sein unter http://localhost:50070 den aktuellen Status des NameNodes und der DataNodes zu einzusehen.

Sollte irgendetwas nicht funktionieren, hilft es bei der Diagnose der Probleme einen Blick in die Log-Dateien zu werfen. Diese werden unter /var/log/hadoop abgelegt. Fehler des NameNode-Dienstes bspw. findet man unter /var/log/hadoop/hadoop--namenode-HOSTNAME.log. Wobei HOSTNAME durch den tatsächlichen Hostnamen bzw. Rechnernamen ersetzt werden muss.

HMR starten

Bevor wir die Dienste für MapReduce starten können, müssen wir im HDFS das Verzeichnis /hadoop/mapred/system anlegen und mapred als Besitzer des Verzeichnisses festlegen.

1
2
sudo -u hdfs hadoop fs -mkdir /hadoop/mapred/system
sudo -u hdfs hadoop fs -chown -R mapred:hadoop /hadoop/mapred

Mittels hadoop fs -ls /hadoop/mapred, können wir uns davon überzeugen, dass der Ordner mit den richtigen Rechten erstellt wurde.

Nun können wir die TaskTracker- und JobTracker-Dienste starten:

1
2
sudo service hadoop-jobtracker start
sudo service hadoop-tasktracker start

Hat alles geklappt, lässt sich unter http://localhost:50030 der Status des TaskTrackers und der JobTracker einsehen.

Benutzer-Verzeichnis erstellen

1
2
sudo -u hdfs hadoop fs -mkdir /user/BENUTZER
sudo -u hdfs hadoop fs -chown BENUTZER:GRUPPE /user/BENUTZER

Ein Beispiel-Programm ausführen

Unsere Hadoop-Installation testen wir mit einem Beispiel-Programm aus, das in der Hadoop-Distribution enthalten ist. Zunächst kopieren wir die Eingabedaten in das verteilte Dateisystem, dann führen wir das Programm aus, und schließlich kopieren wir die Ausgabedaten des Programms auf das lokale Dateisystem.

1
2
3
hadoop fs -put /usr/share/hadoop/templates/conf/ input
hadoop jar /usr/share/hadoop/hadoop-examples-1.0.4.jar grep input output 'dfs[a-z.]+'
hadoop fs -get output output

Mittels cat output/* können wir uns das Ergebnis ansehen.

]>
<![CDATA[Closures in Javascript (und Scala)]> 2012-08-03T21:37:00+02:00 https://mpuppe.de/blog/2012/08/03/closures-in-javascript Closures1 sind meiner Meinung nach eines der interessantesten Sprachkonstrukte von Javascript. Sie erlauben sehr elegante Lösungen, die sonst nicht möglich wären. Was das konkret bedeutet, werde ich an einem Beispiel erläutern. Zunächst möchte ich aber erklären, was Closures sind.

Was ist eine Closure?

Eine Closure beinhaltet eine Funktion und (auch nicht-lokale) Variablen, die innerhalb der Funktion referenziert werden. Closures verändern die Lebensdauer von Variablen. Schauen wir uns dazu ein Beispiel an:

1
2
3
4
5
6
7
8
var x = 0;

function foo() {
  var y = 42;
  return x + y;
}

x = x + 1;

Wir sehen hier zwei Variablen, x und y. Im Hinblick auf die Sichtbarkeit unterscheiden sich die Variablen darin, dass y nur innerhalb der Funktion foo sichtbar ist. Was die Lebensdauer betrifft, so existiert y nur während foo ausgeführt wird.

Sichtbarkeit und Lebensdauer für sich genommen sind keine schwierigen Konzepte. Javascript ist allerdings eine funktionale Sprache und da wird die Sache interessant. Javascript erlaubt es nämlich, Funktionen als Rückgabewert anderer Funktionen zu verwenden. Dazu erweitern wir das obige Beispiel.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function makeFoo() {
  var x = 0;

  function foo() {
    var y = 41;
    x = x + 1;
    return x + y;
  }

  return foo;
}

var bar = makeFoo();
console.log(bar());
console.log(bar());
console.log(bar());

In der Konsole erhält man folgende Ausgabe:

1
2
3
42
43
44

Das ist auf den ersten Blick überraschend, wenn wir davon ausgehen, dass lokale Variablen nur existieren, solange die Funktion, in der sie definiert wurden, ausgeführt wird. Die Variable x hingegen bleibt weiterhin innerhalb der Funktion foo bzw. bar sichtbar und behält zudem ihren Zustand, obwohl makeFoo vollständig abgearbeitet wurde. foo wurde zusammen mit x in eine Closure gepackt.

Praktische Anwendung

Das obige Beispiel ist konstruiert und in der Praxis kaum nützlich. Closures haben aber durchaus sinnvolle Anwendungen. Stellen wir uns einmal vor, wir wollten verschiedene Objekte mit einer eindeutigen Nummer (einer ID) versehen. Dazu wollen wir eine Funktion generateId definieren, die stets eine andere Zahl zurückliefert. Der Einfachheit halber zählen wir von 0 aufwärts. Wir haben mehrere Möglichkeiten dieses Problem zu lösen.

Möglichkeit 1: Globaler Zähler

1
2
3
4
5
6
7
8
9
10
11
var counter = 0;

function generateId() {
  var id = counter;
  counter = counter + 1;
  return id;
}

var obj = {
  id: generateId()
}

Ich halte diese Lösung für nicht sehr sinnvoll. counter ist eine globale Variable. D.h. wir verschmutzen hier den globalen Namensraum mit einer Variable, die nur für eine einzige Funktion benötigt wird. Außerdem kann die Variable praktisch an jeder Stelle im Programm versehentlich geändert werden. Und es wird überhaupt nicht klar, dass counter und generateId` zusammengehören.

Möglichkeit 2: Zähler und Funktion in einem Objekt

1
2
3
4
5
6
7
8
9
10
11
12
var idGenerator = {
  counter: 0,
  generateId: function() {
    var id = this.counter;
    this.counter = this.counter + 1;
    return id;
  }
};

var obj = {
  id: idGenerator.generateId()
}

Diese Lösung ist schon etwas besser, in der Hinsicht, dass wir auf eine globale Variable counter verzichten. Auch wird der Zusammenhang von counter und generateId deutlicher. Allerdings ist diese Variante unhandlich in der Verwendung (idGenerator.generateId()). Außerdem kann auch hier counter an anderer Stelle im Programm geändert werden, obwohl die Variable einzig und allein von der Funktion generateId manipuliert werden sollte.

Möglichkeit 3: Closure

1
2
3
4
5
6
7
8
9
10
11
12
var generateId = (function() {
    var counter = 0;
    return function() {
        var id = counter;
        counter = counter + 1;
        return id;
    };
})();

var obj = {
  id: generateId()
}

Ich halte diese Lösung für die eleganteste. Der Zusammenhang von counter und generateId wird deutlich und der Zähler ist außerhalb der Funktion überhaupt nicht sichtbar.

Anhang: Das Beispiel in Scala

Closures gibt es nicht nur in Javascript, sondern in wohl jeder funktionalen Programmiersprache. Ich habe mich kürzlich im Rahmen einer Vorlesung mit Scala beschäftigen dürfen, daher hier das obige Beispiel in Scala:

1
2
3
4
5
6
7
8
val generateId = (() => {
  var counter = 0
  () => {
    var id = counter;
    counter = counter + 1;
    id
  }
}).apply()
  1. Kurz zur Terminologie: Das deutsche Wort für „function closure“ ist wohl „Funktionsabschluss“. Allerdings hat man wenig Glück, wenn man danach googlet. Mir scheint der Begriff nicht sehr gebräuchlich zu sein, also bleibe ich bei der englischen Bezeichnung.

]>
<![CDATA[XML Schema, Keys und Namespaces]> 2012-05-07T18:03:00+02:00 https://mpuppe.de/blog/2012/05/07/xml-schema Bei der Erstellung von Schemas mit einem Ziel-Namensraum kann man leicht in eine kleine, facepalm-induzierende Falle tappen, die mich heute einiges an Nerven gekostet hat. Um das Problem zu erläutern sehen wir uns am besten ein Beispiel an.

Ohne Namensraum

Beispielinstanz ohne Namensraum (instance.xml) download
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="schema.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Das Dokument besteht aus einem Wurzelement foo und Kindelementen bar. Die bar-Elemente haben ein Attribut id. Nun wollen wir erzwingen, dass id ein Schlüssel ist. Die Instanz wäre dann nicht valide. Das entsprechende XML-Schema muss dazu etwa so aussehen:

Schema ohne Ziel-Namensraum (schema.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Man beachte hier vor allem die Zeilen 14 bis 16, in denen festgelegt wird, dass innerhalb des foo-Elements die bar-Elemente eindeutig durch das Schlüssel-Attribut id identifizierbar sein müssen.

Mit Namensraum

Hier folgt eine Beispiel-Instanz, für die http://foo.bar als Namensraum festgelegt wurde.

Beispielinstanz mit Namensraum (instance_namespace.xml) download
1
2
3
4
5
6
7
8
9
<?xml version="1.0" encoding="UTF-8"?>
<foo xmlns="http://foo.bar"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://foo.bar schema_falsch.xsd">

    <bar id="1"/>
    <bar id="1"/>

</foo>

Man könnte nun meinen, dass es ausreicht, den Namensraum entsprechend auch im Schema anzugeben. Das wurde im folgenden Beispiel in Zeile 3 und 4 getan.

Falsches Schema mit Zielnamensraum (schema_falsch.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    targetNamespace="http://foo.bar" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Das Problem ist aber, dass die Beispiel-Instanz nicht als invalide erkannt wird, sondern klaglos akzeptiert wird. Warum ist das so? Das Problem ist der XPath-Ausdruck in Zeile 17. Damit werden alle bar-Elemente selektiert und sichergestellt, dass diese eine eindeutige id haben. Unser Ziel-Namensraum ist allerdings http://foo.bar. Das heißt wir wollen nicht bar-Elemente selektieren, sondern http://foo.bar:bar-Elemente. Das richtige Schema sieht deshalb so aus:

Richtiges Schema mit Zielnamensraum (schema_richtig.xsd) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns="http://foo.bar"
    xmlns:foobar="http://foo.bar"
    targetNamespace="http://foo.bar" elementFormDefault="qualified">
    <xs:element name="foo">
        <xs:complexType>
            <xs:sequence>
                <xs:element name="bar" maxOccurs="unbounded">
                    <xs:complexType>
                        <xs:attribute name="id"/>
                    </xs:complexType>
                </xs:element>
            </xs:sequence>
        </xs:complexType>

        <xs:key name="idKey">
            <xs:selector xpath="foobar:bar"/>
            <xs:field xpath="@id"/>
        </xs:key>
    </xs:element>
</xs:schema>

Zu beachten sind die Zeilen 4 und 18. In Zeile 4 wird definiert, dass das Kürzel foobar für den Namensraum http://foo.bar steht und in Zeile 18 wird dieses Kürzel verwendet, um den korrekten XPath-Ausdruck zu konstruieren.

]>
<![CDATA[resumeLater 0.1 veröffentlicht]> 2012-03-29T11:53:00+02:00 https://mpuppe.de/blog/2012/03/29/resumelater-0-dot-1

resumeLater ist eine Firefox-Erweiterung. Sie erlaubt es, die aktuelle Position in einem Youtube-Video zu speichern. Später kann die Wiedergabe an exakt der selben Stelle fortgesetzt werden. Das ist vor allem praktisch bei längeren Videos (etwa von Vorträgen), die man nicht an einem Stück schauen möchte. Download bei addons.mozilla.org.

In Version 0.1 wird die Video-Liste nur lokal im Browser gespeichert. Für Version 1.0 ist die Möglichkeit geplant, die Videos mit Bookmarking-Sites wie Delicious und Pinboard zu synchronisieren. Mittelfristig ist auch eine Android-App denkbar.

Außerdem vorgesehen:

  • Unterstützung weiterer Video-Seiten (Vimeo, Dailymotion etc.)
  • Deaktivieren bei Private Browsing

Quellcode und Issue-Tracker auf Github.

]>