Complete mit readline

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Derzeit arbeite ich an einem kleinen Projekt ... dann dachte ich mir "Complete wäre ein nettes Feature" ... und schon wurde aus einer Mücke ein Elefant ... ;-)

Ich hab die die Dokumentationen bei Python und die der Gnu readline Bibliothek durchgelesen ...
Ich hab im Internet nach Beispielen gesucht ...

Entweder ich nicht richtig gesucht, hab nichts brauchbares gefunden oder bin nicht in der Lage das Gefundene zu verstehen ...
(Wobei ich letzteres doch bezweifle ;-) )

Was bedeuten die Parameter "text" und "state", die die Funkltion übergeben bekommt?
Und was soll die Funktion mit diesen Parametern machen?

Nachdem ich mir seit 3 Tagen den Kopf zerbreche wäre ich für Hilfe zu dem Thema dankbar.
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@midan23: Also die Dokumentation sagt:

Code: Alles auswählen

set_completer([function]) -> None
Set or remove the completer function.
The function is called as function(text, state),
for state in 0, 1, 2, ..., until it returns a non-string.
It should return the next possible completion starting with 'text'.
Was genau ist daran unverständlich? Und warum hast Du das nicht einfach mal ausproblert?

Code: Alles auswählen

#!/usr/bin/env python3
import readline


def completer(text, state):
    #print(text, state)
    return f"{text}{state}" if state < 5 else None


def main():
    readline.set_completer(completer)
    readline.parse_and_bind("tab: complete")
    text = input("Eingabe: ")
    print(text)


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Ich hab so einiges ausprobiert ... darunter auch so etwas:

Code: Alles auswählen

def completer(self, text, state):
    result = [word for word in self.get_words() if word.startswith(text)]
    if len(result) == 1:
        result = [f"{result[0]} "]
    result.append(None)
    return result[state]
Aber sobald es mehr als ein Wort ist das vervollständigt werden soll weiß ich nicht, wie es gehen soll ...
Damit meine ich so was wie: command subcommand ...
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@midan23: Das musst Du halt programmieren. Das Du erst schaust wie der komplette Eingabepuffer aussieht, dass der mit "command" anfängt, und dann entsprechend die Wortliste für "subcommand" und/oder mögliche Optionen für "command" anbietet. Die komplette bisherige Eingabe bekommt man mit `readline.get_line_buffer()`.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Und was hat "state" für eine Bedeutung?
Benutzeravatar
__blackjack__
User
Beiträge: 13077
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@midan23: Ich verstehe die Frage nicht so ganz, Du verwendest das doch. Das ist eine fortlaufende Zahl um die verschiedenen Varianten abzufragen. `readline` ruft die Funktion nach einer Anfrage zur Vervollständigung so oft auf bis die keine Zeichenkette mehr liefert. Und man muss bei jedem Aufruf ja wissen wie oft schon aufgerufen wurde. Und das muss man sich nicht selbst merken, das liefert `state` von aussen schon. Das ist aus Python-Sicht natürlich eine etwas komische API aber `libreadline` ist eine C-Bibliothek und die Entwickler von dem Modul haben sich entschieden die möglichst 1:1 weiterzureichen.

Ich würde da auch nicht bei jedem Aufruf alle Möglichkeiten in einer Liste packen nur um dann *eine* davon auszuwählen. Bei `state` gleich 0 kann man die Möglichkeiten erstellen und sich einfach merken.

Was genau willst Du da eigentlich machen? Würde das `cmd`-Modul aus der Standardbibliothek Dein Leben eventuell vereinfachen? Oder das python-prompt-toolkit eventuell noch mehr vereinfachen (und bunter machen)?
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Beides habe ich ausprobiert ... und irgendwie bin ich mit keines der beiden Module/Klassen wirklich zufrieden ...

Mit dem "cmd"-Modul hab ich einen Befehlsinterpreter gebaut ... das Ergebnis ist ein Monster das ich nicht wirklich verstehe und damit auch nicht ändern kann.
Ich hab zwar versucht "cmd" mit "argparse" zu nutzen, aber sobald die Parser komplexer werden wird es unübersichtlich ... und das obwohl vieles davon im Prinzip nur Copy/Paste ist ...

"python-prompt-toolkit" bietet zwar viel, aber die Befehle müsste ich trotzdem erst überprüfen.
"cmd2" hab ich auch schon versucht ... Bietet alles von "cmd" und "argparse" mit Autocomplete ... aber die mangelnde Übersicht bleibt ... und die Parser werden auf Klassenebene erstellt, was mir auch nicht wirklich gefällt.

Nachdem ich nichts wirklich brauchbares gefunden hatte, habe ich angefangen mein eigenes Framework zu bauen ...
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

Das Wichtigste zuerst:

@__blackjack__: Danke für deine Hilfe

Hat etwas gedauert aber letzten Endes habe ich verstanden, das die Funktion die an "readline.set_completer" übergeben wird vom Prinzip her nicht anderes ist als ein Generator der nach und nach die Elemente einer Liste aus Zeichenketten zurück gibt. Dabei ist der Parameter "state" der Index für die Liste. Wenn "state" gleich 0 ist sollte man die Liste erstellen und das letzte Element der Liste sollte keine Zeichenkette sein.

Und ich konnte sogar noch was anderes lernen: Das es Comprehensions für Listen gibt wusste ich ... das es aber auch welche für dicts und sets gibt war mir neu ...

Wie dem auch sei, meine CMD-Alternative häng ich mal unten dran. Vielleicht kann jemand diese brauchen ...

Code: Alles auswählen

class MyCmd():

    # ---< def __init__(self, completekey=None) >---
    def __init__(self, completekey=None):
        self.running = True
        self.prompt = "> "
        self._cmds = dict()
        for cmd in [s[3:] for s in dir(self.__class__) if s.startswith("do_")]:
            key = cmd.replace("_", " ")
            self._cmds[key] = {"function": getattr(self, f"do_{cmd}")}
            completer = getattr(self, f"complete_{cmd}", None)
            if completer:
                self._cmds[key]["completer"] = completer
        self.completekey = completekey if completekey else "tab"
        self._old_completer = readline.get_completer()
        readline.set_completer(self.completer)
        readline.parse_and_bind(f"{self.completekey}: complete")
        self._completions = list()

    # ---< def normalize(self, line, keep_last_space) >---
    def normalize(self, line, keep_last_space):
        result = ""
        if line:
            last = keep_last_space and (line[-1] == " ")
            result = " ".join(line.split())
            if last:
                result = f"{result} "
        return result

    # ---< def completer(self, text, state) >---
    def completer(self, text, state):
        if not state:
            self._completions = list()
            line = self.normalize(readline.get_line_buffer(), True)
            spaces = line.count(" ")
            if spaces == 0:
                self._completions = list({s.split()[0] for s in self._cmds if s.startswith(text)})
            elif spaces == 1:
                cmd = line.split()[0]
                if cmd in self._cmds:
                    completer = self._cmds[cmd].get("completer", None)
                    if completer:
                        self._completions = [s for s in completer(state) if s.startswith(text)]
                else:
                    subcmds = list({s.split()[1] for s in self._cmds if " " in s})
                    self._completions = [s for s in subcmds if s.startswith(text)]
            else:
                cmd = " ".join(line.split()[:2])
                if cmd in self._cmds:
                    completer = self._cmds[cmd].get("completer", None)
                    if completer:
                        self._completions = [s for s in completer(state) if s.startswith(text)]

            if len(self._completions) == 1:
                self._completions = [f"{self._completions[0]} "]
            self._completions.append(None)
        return self._completions[state]

    # ---< def cmdloop(self) >---
    def cmdloop(self):
        self.preloop()
        while True:
            line = self.get_line()
            if not self.running:
                break
            line = self.precmd(line)
            self.onecmd(line)
            self.postcmd(line)
        self.postloop()

    # ---< def get_line(self) >---
    def get_line(self):
        line = ""
        try:
            line = input(self.prompt)
        except (EOFError, KeyboardInterrupt):
            print()
            self.running = False
        return self.normalize(line, False)

    # ---< def preloop(self) >---
    def preloop(self):
        pass

    # ---< def postloop(self) >---
    def postloop(self):
        readline.set_completer(self._old_completer)

    # ---< def precmd(self, line) >---
    def precmd(self, line):
        return line

    # ---< def postcmd(self, line) >---
    def postcmd(self, line):
        pass

    # ---< def onecmd(self, line) >---
    def onecmd(self, line):
        if line:
            cmd, _, rest = line.partition(" ")
            if cmd not in self._cmds:
                subcmd, _, rest = rest.partition(" ")
                cmd = f"{cmd} {subcmd}"
            func = self._cmds.get(cmd, None)
            if func:
                func["function"](rest)
            else:
                self.default(line)

    # ---< def default(self, line) >---
    def default(self, line):
        print(f"Unknown Syntax: {line}")
Antworten