Hallo allerseits!
Ich schreibe gerade ein Python-Skript, das an verschiedenen Stellen Eingaben vom User verlangt, unter anderem Dateinamen.
Ich benutze raw_input und habe das readline-Modul eingebunden. Das ist schonmal nicht schlecht, man kann seine Eingabe bequem editieren. Nun haette ich gerne, dass Dateinamen beim Druecken der TAB-Taste vervollstaendigt werden, wie in einer Unix-Shell.
Ich habe
readline.parse_and_bind("tab: complete")
gesetzt, was aber standargmaessig versucht, Python-Kommandos zu vervollstaendigen.
Jetzt muss ich mir eine eigene completer-Funktion schreiben, oder? Wie mache ich das? Oder gibt's noch eine ganz andere Moeglichkeit?
Gruss,
Rebecca
Dateinamenvervollstaendigung in der Kommandozeile (Unix)
Ich habe da von noch nichts fertigen gehört, wirst du also wohl selbst schreiben müssen.
Verwenden kannst du dazu os.listdir() und prüfst ob der teilweise dateiname mit den ergebnissen matcht ...
Aber vielleicht hat ja jemand eine bessere idee ...
Verwenden kannst du dazu os.listdir() und prüfst ob der teilweise dateiname mit den ergebnissen matcht ...
Aber vielleicht hat ja jemand eine bessere idee ...
- Rebecca
- User
- Beiträge: 1662
- Registriert: Freitag 3. Februar 2006, 12:28
- Wohnort: DN, Heimat: HB
- Kontaktdaten:
Ich bin jetzt ein wenig schlauer...
Eine completer-Funktion sieht wie folgt aus:
completer(text, state)
Sie soll die Vervollstaendigung Nummer 'state' von String 'text' zurueckliefern oder 'None', wenn es keine (weitere) Vervolltsaendigung (mehr) gibt.
Eine Vervollstaendigung innerhalb des aktuellen Verzeichnisses ./ hinzubekommen ist nicht so schwierig. Dummerweise scheint 'text' nicht exakt das zu sein, was der Benutzer eingibt, sondern die Eingabe wurde schon bereinigt. Das fuehrt zu einem Problem mit slashes:
Die Eingabe "/home/rbreu" fuehrt zu text = "rbreu",
"/home/rbreu/" fuehrt zu text = ""
und so weiter. :-(
Kann man das irgendwie beeinflussen? Das Modul readline hat ja noch einiges an Methoden zu bieten, nur ist die Python-Dokumentation ziemlich knapp. *nichvielversteht*
Hier meine bisherige completer-Funktion:
Eine completer-Funktion sieht wie folgt aus:
completer(text, state)
Sie soll die Vervollstaendigung Nummer 'state' von String 'text' zurueckliefern oder 'None', wenn es keine (weitere) Vervolltsaendigung (mehr) gibt.
Eine Vervollstaendigung innerhalb des aktuellen Verzeichnisses ./ hinzubekommen ist nicht so schwierig. Dummerweise scheint 'text' nicht exakt das zu sein, was der Benutzer eingibt, sondern die Eingabe wurde schon bereinigt. Das fuehrt zu einem Problem mit slashes:
Die Eingabe "/home/rbreu" fuehrt zu text = "rbreu",
"/home/rbreu/" fuehrt zu text = ""
und so weiter. :-(
Kann man das irgendwie beeinflussen? Das Modul readline hat ja noch einiges an Methoden zu bieten, nur ist die Python-Dokumentation ziemlich knapp. *nichvielversteht*
Hier meine bisherige completer-Funktion:
Code: Alles auswählen
def complete(text, state):
"""Return the next possible completion for 'text'.
This is called successively with state == 0, 1, 2, ... until it
returns None. The completion should begin with 'text'.
"""
completions = []; #A list of all possible completions
#The directory we are looking in:
the_dir = os.path.join(".", os.path.dirname(text));
#print ">" + text + "===>" + the_dir; #for test purposes
#A list of all files in the_dir:
files = dircache.listdir(the_dir);
#Find the entries of 'files' which mach 'text':
for entry in files:
os.path.join(the_dir, entry);
if entry.startswith(text):
if os.path.isdir(entry):
entry = os.path.join(entry, "");
completions.append(entry);
return completions[state];
Hmm .. also ich bin gerade unter Windows (mit WConio) dabei ein Consolen Overlay zu implementieren ... deswegen kann ich nicht genau sagen ob und wie es mit 'readline' direkt zu realisieren ist.
Wenn du aber die Eingabe hast, und den Cursor bewegst, und in der Lage bist den CursorIndex auf dem Inputstring herrauszufinden, dann kannst du da genau ansetzen:
Da ich gleich Feierabend habe, hacke ich schnell mal meine Ansätze hier hinnein. Sorry für die grausame Struktur und Rechtschreibfehler .
1. String bis zur aktuellen CursorPosition nehmen
(Bsp: /data/test/ <- Cursor auf 'e' von Test = Index von 7
neuer String = derString[:7]
neuer String ist somit: /data/te
2. wenns man genau wie auf den meisten Plattformen haben möchte, müsste man noch vom Ende aus nur den Teil nehmen, der kein Leerzeichen enthält
(Bsp: "bla /data/test" ... zu "/data/te")
3. den String nun mit os.path.basename und .dirname aufsplitten
(Bsp: dir_ = /data
base = te)
4. schauen ob "dir_" ein vollständiger Root-Pfad ist, oder ob er von der aktuellen Position aus laufen soll (falls er von der aktuellen Position aus laufen soll, mit os.path.join(os.getcwd(), dir_) vervollständigen)
5. tjoa .. nun kann man halt aus dem "dir_" Pfad mit os.listdir(dir_) die Elemente holen, und mittels Regulären Ausdruck (ansonsten tut's ein "if elemt.find(base) == 0" auch) am besten prüfen, ob "base" der Anfang des Elementes halt ist
etwas gemein ist es, wenn "base" natürlich ein Leerstring ist ... das ist ein Soderfall, der eine Iteration über "alle" Elemente erlauben sollte
6. entsprechend dann den Eingabestring (den nun gefunden Path) in der Commandline erweitern
Noch nicht gelöst ist hiermit eine "Iteration" über die einzelnen Treffer.
Z.B:
/data/test
/data/test_2
/data/test_3
/data/technik
... ein solche tabbare Verzeichnisiteration erfordert ein paar Merkschritte, sowie die Sortierung der Verzeichnisinhaltsliste.
Ich versuche die Tage die WConio Lösung hier zu posten.
Gruß
>>Masaru<<
Wenn du aber die Eingabe hast, und den Cursor bewegst, und in der Lage bist den CursorIndex auf dem Inputstring herrauszufinden, dann kannst du da genau ansetzen:
Da ich gleich Feierabend habe, hacke ich schnell mal meine Ansätze hier hinnein. Sorry für die grausame Struktur und Rechtschreibfehler .
1. String bis zur aktuellen CursorPosition nehmen
(Bsp: /data/test/ <- Cursor auf 'e' von Test = Index von 7
neuer String = derString[:7]
neuer String ist somit: /data/te
2. wenns man genau wie auf den meisten Plattformen haben möchte, müsste man noch vom Ende aus nur den Teil nehmen, der kein Leerzeichen enthält
(Bsp: "bla /data/test" ... zu "/data/te")
3. den String nun mit os.path.basename und .dirname aufsplitten
(Bsp: dir_ = /data
base = te)
4. schauen ob "dir_" ein vollständiger Root-Pfad ist, oder ob er von der aktuellen Position aus laufen soll (falls er von der aktuellen Position aus laufen soll, mit os.path.join(os.getcwd(), dir_) vervollständigen)
5. tjoa .. nun kann man halt aus dem "dir_" Pfad mit os.listdir(dir_) die Elemente holen, und mittels Regulären Ausdruck (ansonsten tut's ein "if elemt.find(base) == 0" auch) am besten prüfen, ob "base" der Anfang des Elementes halt ist
etwas gemein ist es, wenn "base" natürlich ein Leerstring ist ... das ist ein Soderfall, der eine Iteration über "alle" Elemente erlauben sollte
6. entsprechend dann den Eingabestring (den nun gefunden Path) in der Commandline erweitern
Noch nicht gelöst ist hiermit eine "Iteration" über die einzelnen Treffer.
Z.B:
/data/test
/data/test_2
/data/test_3
/data/technik
... ein solche tabbare Verzeichnisiteration erfordert ein paar Merkschritte, sowie die Sortierung der Verzeichnisinhaltsliste.
Ich versuche die Tage die WConio Lösung hier zu posten.
Gruß
>>Masaru<<
Man kann angeben welche Zeichen als "Wortgrenzen" für die Vervollständigung genommen werden. Da scheint der '/' dabei zu sein. Wenn man es zum Beispiel nur auf Leerzeichen beschränkt, dann klappt's:Rebecca hat geschrieben:Dummerweise scheint 'text' nicht exakt das zu sein, was der Benutzer eingibt, sondern die Eingabe wurde schon bereinigt. Das fuehrt zu einem Problem mit slashes:
Die Eingabe "/home/rbreu" fuehrt zu text = "rbreu",
"/home/rbreu/" fuehrt zu text = ""
und so weiter.
Kann man das irgendwie beeinflussen? Das Modul readline hat ja noch einiges an Methoden zu bieten, nur ist die Python-Dokumentation ziemlich knapp. *nichvielversteht*
Code: Alles auswählen
import os
import readline
def completer(text, state, _names=list()):
path, incomplete_part = os.path.split(text)
if state == 0:
_names[:] = filter(lambda name: name.startswith(incomplete_part),
os.listdir(os.path.join(os.path.curdir, path)))
return os.path.join(path, _names[state])
def main():
readline.set_completer_delims(' ')
readline.set_completer(completer)
readline.parse_and_bind('tab: complete')
while True:
line = raw_input('> ')
print ':', line
- Rebecca
- User
- Beiträge: 1662
- Registriert: Freitag 3. Februar 2006, 12:28
- Wohnort: DN, Heimat: HB
- Kontaktdaten:
Aha! Genau das war's, was mir gefehlt hat, super! Ich hatte schon vermutet, dass es damit zu tun hat, aber irgendwas hatte ich wohl mit set_completer_delims noch falsch gemacht.BlackJack hat geschrieben: Man kann angeben welche Zeichen als "Wortgrenzen" für die Vervollständigung genommen werden. Da scheint der '/' dabei zu sein. Wenn man es zum Beispiel nur auf Leerzeichen beschränkt, dann klappt's:
Code: Alles auswählen
readline.set_completer_delims(' ')
Mit den richtigen Delimitern konnte ich dann auch feststellen, dass meine oben gepostete Funktion noch nicht so ganz richtig ist. Bei mir sieht's jetzt so aus:
Code: Alles auswählen
def complete(text, state):
"""Return the next possible completion for 'text'.
This is called successively with state == 0, 1, 2, ... until it
returns None. The completion should begin with 'text'.
"""
completions = []; #A list of all possible completions
(the_dir, incomplete) = os.path.split(text);
#A list of all files in the_dir:
files = dircache.listdir(os.path.join(".", the_dir));
#Find the entries of 'files' which mach 'text':
for entry in files:
if entry.startswith(incomplete):
if os.path.isdir(os.path.join(the_dir, entry)):
entry = os.path.join(entry, "");
completions.append(os.path.join(the_dir, entry));
return completions[state];
Ich denke, damit kann ich leben. Danke nochmals.BlackJack hat geschrieben: Es scheint das ich mir meinen eigenen `dircache` gebaut habe, da ich das Modul nicht im Kopf hatte. Wobei ich die Dateien wirklich nur einmal lese und `dircache` jedesmal zumindest testet ob sich die Einträge geändert haben. Ich vermute meine Lösung ist schneller und macht bei Verzeichnissen mit vielen Einträgen einen merkbaren Unterschied.
Rebecca,
die sich jetzt endlich beim Testen ihres Skripts nicht mehr die Finger wundtippen muss
- Rebecca
- User
- Beiträge: 1662
- Registriert: Freitag 3. Februar 2006, 12:28
- Wohnort: DN, Heimat: HB
- Kontaktdaten:
Argh, wie bescheuert ist das denn? Das readline-Module ignoriert alle Exceptions einer completer-Funktion!
Ich habe gerade einen halbe Ewigkeit damit verbracht, harauszufinden, warum die Vervollstaendigung in meinem neuen Programm nicht mehr funktioniert, wo sie doch in einem anderen Programm so gut funktioniert hat. Hab mich dann gewundert, warum die obige complete-Funktion anscheinend nicht ueber Zeile 10 nicht hinauskam -- hatte vergessen, os zu importieren.
Ich habe gerade einen halbe Ewigkeit damit verbracht, harauszufinden, warum die Vervollstaendigung in meinem neuen Programm nicht mehr funktioniert, wo sie doch in einem anderen Programm so gut funktioniert hat. Hab mich dann gewundert, warum die obige complete-Funktion anscheinend nicht ueber Zeile 10 nicht hinauskam -- hatte vergessen, os zu importieren.
Hallo,
tut mir leid, dass ich das Thema wieder ausgrabe, aber ich habe durch die Suche eure Skripte gefunden, weil ich auch eine Textvervollständigung brauche. Allerdings würde ich sie auch gerne Verstehen Ich nehme jetzt einfach mal BlackJacks Code:
LG
Tobsl
tut mir leid, dass ich das Thema wieder ausgrabe, aber ich habe durch die Suche eure Skripte gefunden, weil ich auch eine Textvervollständigung brauche. Allerdings würde ich sie auch gerne Verstehen Ich nehme jetzt einfach mal BlackJacks Code:
Code: Alles auswählen
import os
import readline
def completer(text, state, _names=list()):
path, incomplete_part = os.path.split(text)
print path #1
print incomplete_part #1
print state #2
if state == 0:
_names[:] = filter(lambda name: name.startswith(incomplete_part),
os.listdir(os.path.join(os.path.curdir, path))) #3
print _names #4
return os.path.join(path, _names[state]) #5
def main():
readline.set_completer_delims(' ') #6
readline.set_completer(completer)
readline.parse_and_bind('tab: complete') #7
while True:
line = raw_input('> ')
print ':', line
- Wieso wird hier kein Pfad/Name ausgegeben?
- Für was steht state?(eventuell die Anzahl der enthaltenend Dateien im Ordner?)
- Wieso schreibst du _name[:]? Das liefert doch eine flache Kopie der Liste, oder? Für was ist das notwending?
- Wie wird denn state weitergeben?
- Wie kann man denn keinen Delimiter angeben bzw einen, der keinen "Einfluss" hat, denn ein Leerzeichen ist - wie ich finde - unpraktisch, da es ja auch Order/Dateinamen mit Leerzeichen gibt.
- Wie kommt man auf tab: complete? Gibts auch andere Einstellungsmöglichkeiten?
LG
Tobsl
Hi! I'm a .signature virus! copy me into your .signature file to help me spread!
re 1: wo ist "hier"?
re 2: erwähnte Rebecca doch vor zwei Jahren: Der n-te Vorschlag.
re 3: Die Kopie liefert es nur, wenn es auf der rechten Seite vom "=" steht. Links führt es dazu, das bei der Liste alle Elemente durch die Elemente rechts vom "=" ersetzt werden. BlackJacks Code nutzt einen fiesen Trick, um eine private globale Variable zu bekommen, indem er eine Liste manipuliert, die der Standardwert eines Parameters ist, wissend, dass die Standardwerte von allen aufgerufenen Funktionen gemeinsam benutzt werden.
re 4: state wird von libreadline übergeben.
re 5: keine Ahnung, vielleicht mit ""? Die Python-Funktion ruft rl_completer_word_break_characters() auf, dafür wird es doch wohl eine Dokumentation geben...
re 6: Die Funktion ruft rl_parse_and_bind() auf. Auch dafür gibt es Dokumentation... Es ist das init-file-Format. Man könnte z.B. readline.parse_and_bind('A: "Hallo"') benutzen und bei jedem großen A wird dann Hallo geschrieben. Es funktioniert auch mit mehreren Zeichen, etwa readline.parse_and_bind('"mfg": "Mit freundlichen Grüßen"') (aber nicht mit dem Python 2.5.1, das OS X 10.5 beiliegt)
Stefan
re 2: erwähnte Rebecca doch vor zwei Jahren: Der n-te Vorschlag.
re 3: Die Kopie liefert es nur, wenn es auf der rechten Seite vom "=" steht. Links führt es dazu, das bei der Liste alle Elemente durch die Elemente rechts vom "=" ersetzt werden. BlackJacks Code nutzt einen fiesen Trick, um eine private globale Variable zu bekommen, indem er eine Liste manipuliert, die der Standardwert eines Parameters ist, wissend, dass die Standardwerte von allen aufgerufenen Funktionen gemeinsam benutzt werden.
re 4: state wird von libreadline übergeben.
re 5: keine Ahnung, vielleicht mit ""? Die Python-Funktion ruft rl_completer_word_break_characters() auf, dafür wird es doch wohl eine Dokumentation geben...
re 6: Die Funktion ruft rl_parse_and_bind() auf. Auch dafür gibt es Dokumentation... Es ist das init-file-Format. Man könnte z.B. readline.parse_and_bind('A: "Hallo"') benutzen und bei jedem großen A wird dann Hallo geschrieben. Es funktioniert auch mit mehreren Zeichen, etwa readline.parse_and_bind('"mfg": "Mit freundlichen Grüßen"') (aber nicht mit dem Python 2.5.1, das OS X 10.5 beiliegt)
Stefan
- Rebecca
- User
- Beiträge: 1662
- Registriert: Freitag 3. Februar 2006, 12:28
- Wohnort: DN, Heimat: HB
- Kontaktdaten:
Hey, das war mein erster Post hier, da werd ich ja ganz nostalgisch!
Wenn die print-Ausgaben gemeint sind: Die werden vlt. einfach genau so ignoriert wie meine Exceptions in meinem letzten Post... Ist vlt. ein Schutz davor, die Kommandozeile nicht voellig unbrauchbar zu machen...sma hat geschrieben:re 1: wo ist "hier"?
Stimmt, damit kann man quasi statische Variablen aus C nachbauen. Schade, dass es so ein Hack ist...BlackJacks Code nutzt einen fiesen Trick, um eine private globale Variable zu bekommen
Offizielles Python-Tutorial (Deutsche Version)
Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei
Urheberrecht, Datenschutz, Informationsfreiheit: Piratenpartei