Verzeichnis/Dateiliste als tex-Datei ausgeben

Du hast eine Idee für ein Projekt?
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Nur noch einmal ein Nachtrag, um das Beispiel nicht so stehen zu lassen. Dieses None war kein None, sondern nur ein leerer String, einen Teil der Statements auf elif und die Sortierung geändert.

Code: Alles auswählen

def wandle_datum(data):
    return int(time.mktime(time.strptime(data, "%d.%m.%Y")))

def suche_files(durchlaufe, auswahl, endung, vondate, bisdate):
    os.stat_float_times(False)
    file_dict = {}
    rueckgabe = []

    for verzeichnisse, unterordner, dateien in os.walk(durchlaufe):
        for dateinamen in dateien:
            extension = os.path.splitext(dateinamen)[1]
            if extension == endung:
                if auswahl == "alphabetisch":
                    zeitpunkt = os.path.getctime(os.path.join(verzeichnisse, dateinamen))
                    fuelledict = {os.path.join(verzeichnisse, dateinamen) : zeitpunkt}
                    file_dict.update(fuelledict)
                elif auswahl == "angelegt":
                    zeitpunkt = os.path.getctime(os.path.join(verzeichnisse, dateinamen))
                elif auswahl == "aenderung":
                    zeitpunkt = os.path.getmtime(os.path.join(verzeichnisse, dateinamen))
                elif auswahl == "aufgerufen":
                    zeitpunkt = os.path.getatime(os.path.join(verzeichnisse, dateinamen))

                if auswahl == "angelegt" or auswahl == "aenderung" or auswahl == "aufgerufen":
                    if vondate == "-" or bisdate == "-":
                        fuelledict = {zeitpunkt : os.path.join(verzeichnisse, dateinamen)}
                        file_dict.update(fuelledict)
                    elif vondate != "-" and bisdate  != "-":
                        if zeitpunkt >= wandle_datum(vondate) and zeitpunkt <= wandle_datum(bisdate):
                            fuelledict = {zeitpunkt : os.path.join(verzeichnisse, dateinamen)}
                            file_dict.update(fuelledict)

    # Reihenfolge - files und filetimes - plus lambda und lower
    if auswahl == "alphabetisch":
        sortiert = sorted(file_dict.items(), key = lambda i: i[0].lower(), reverse = 1)
        
        for files, filetimes in sortiert:
            zeitformat = time.strftime("%d.%m.%Y", time.localtime(filetimes))
            rueckgabe.append("{0:s} - {1:s}".format(files, zeitformat))

    # Reihenfolge - filetimes und files
    elif auswahl == "angelegt" or auswahl == "aenderung" or auswahl == "aufgerufen":
        sortiert = sorted(file_dict.items(), reverse = 1)
        
        for filetimes, files in sortiert:
            zeitformat = time.strftime("%d.%m.%Y", time.localtime(filetimes))
            rueckgabe.append("{0:s} - {1:s}".format(files, zeitformat))

    return rueckgabe

class Dateisuche:

    def __init__(self):
        self.fenster = Tk()
        self.set_auswahl = StringVar(value="1")
        self.set_endung  = StringVar(value="1")
        self.textfeld = None
        self.auswahl = "alphabetisch"
        self.endung  = "py"
        self.vondate = None
        self.bisdate = None
        self.durchlaufe = "."

    def main(self):
         if self.vondate is None or self.vondate == "":
             self.vondate = "-"
         if self.bisdate is None or self.bisdate == "":
             self.bisdate = "-"
             
         gefunden = suche_files(self.durchlaufe, self.auswahl,
                    self.endung, self.vondate, self.bisdate)

         self.textfeld.delete(1.0, END)

         for dateien in gefunden:
             self.textfeld.insert(1.0, "{}\n".format(dateien))

# Der rest vom Code ist unverändert. 
TuXX
User
Beiträge: 24
Registriert: Sonntag 25. Juni 2017, 17:52

Erst einmal vielen Dank für das rege Feedback auf meine Frage. Mittlerweile bin ich mit meinem Skript etwas weiter gekommen. Siehe Beispiel:

Code: Alles auswählen

import os
for folderName, subfolders, filenames in os.walk('C:\\Techdok\\'):
    texfile = open ('Verzeichnislist.txt', 'a')
    texfile.write(folderName + "\n")
    for subfolder in subfolders:
        texfile.write(subfolder + "\n")
    for filename in filenames:
        texfile.write(filename + "\n")
    texfile.close()
Dieses Skript gibt folgendes aus:

C:\Techdok (hier müssen noch die ersten drei Zeichen entfernt werden, damit ich ein schönes erstes Lesezeichen kreiren kann!)
C.\Techdok\Ordner 1 (hier kann ich für das Unterlesezeichen nur den hintersten Ordner (in diesem Fall Ordner 1 gebrauchen)
Datei.pdf (hier müssen ebenfalls wieder die letzten vier Zeichnen entfernt werden, um ein schickes Lesezeichen zu haben)

Aktuell habe ich noch keine Lösung dafür gefunden, wie ich Pfade wie z.B. C:\Techdoc\Ordner1\Datei1.pdf in eine Datei schreiben kann. Wie sieht es bei den für Tex notwendigen geschweiften und eckigen Klammern aus. Gibt es hier eine spezielle Darstellungsweise um diese korrekt in eine Datei schreiben zu können?

Im Voraus vielen Dank für euren Input!
Zuletzt geändert von Anonymous am Dienstag 4. Juli 2017, 22:00, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@TuXX: Warum öffnest und schliesst Du denn die Datei für jedes Verzeichnis? Es wäre irgendwie sinnvoller die einmal am Anfang zu öffnen und dann am Ende zu schliessen. Das hattest Du doch auch schon mal so. Am besten verwendet man beim Öffnen die ``with``-Anweisung, damit die Datei automatisch und sicher geschlossen wird wenn der Programmfluss den ``with``-Block verlässt.

Wie das mit dem zusammensetzen von Pfaden geht, wurde hier im Thema auch schon gezeigt. Melewo hat da einen Beispielcode gezeigt, der zudem auch schon das filtern nach PDF-Dateien enthält.

Eckige und geschweifte Klammern in eine Datei zu schreiben ist kein Hexenwerk. Wo ist denn das Problem dabei? Ein tatsächliches Problem ist eher sicherzustellen das Zeichen die für LaTeX eine besondere Bedeutung haben, keine Probleme machen wenn sie in normalen Daten vorkommen. Dazu kann man sich selbst eine Funktion schreiben die solche Zeichen vor LaTeX entsprechend schützt, oder etwas fertiges hernehmen. Aber das hatten wir ja auch schon mal angesprochen.

Ist Dir denn das Ziel schon klar? Denn sonst kannst Du nicht darauf hinarbeiten.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

TuXX hat geschrieben:C:\Techdok (hier müssen noch die ersten drei Zeichen entfernt werden, damit ich ein schönes erstes Lesezeichen kreiren kann!)
C.\Techdok\Ordner 1 (hier kann ich für das Unterlesezeichen nur den hintersten Ordner (in diesem Fall Ordner 1 gebrauchen)
Datei.pdf (hier müssen ebenfalls wieder die letzten vier Zeichnen entfernt werden, um ein schickes Lesezeichen zu haben)
Dafür verwende ich bisher replace(). Bei immer gleichbleibenden Zeichenfolgen scheint es mir die einfachste Lösung zu sein. Ob die beste, weiß ich hingegen noch nicht, wäre nur meine Antwort auf Zeile 1 und 3 vom Zitat.

Code: Alles auswählen

beispiel = "beispieldatei.pdf"
ausgabe = "# Ausgabe: {0:s}".format(beispiel.replace(".pdf", ""))

print(ausgabe)
# Ausgabe: beispieldatei
Und den Codeabschnitt habe ich oben noch herausgekürzt, da der nur einmal benötigt wird und in einer Variablen gespeichert werden kann, die die dann für alle anderen Statements benutzt wird.

Code: Alles auswählen

os.path.join(verzeichnisse, dateinamen)
BlackJack

@Melewo: `replace()` ersetzt alle vorkommen, auch innerhalb der Zeichenkette, ist also nicht die passende Operation um am Ende eine gewisse Anzahl von Zeichen zu entfernen. Da würde ich „slicing“ (``beispiel[:-len('.pdf')]``) oder `os.path.splitext()` verwenden.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

@BlackJack: Danke, musste ich doch gleich einmal probieren.

Code: Alles auswählen

beispiel = ".\\beispieldatei.pdf"
ausgabe = "# Ausgabe: {0:s}".format(beispiel[2:-4])

print(ausgabe)
# Ausgabe: beispieldatei

ausgabe = "# Ausgabe: {0:s}".format(beispiel[len('.\\'):-len('.pdf')])

print(ausgabe)
# Ausgabe: beispieldatei
BlackJack

@Melewo: Da ist jetzt der Anfang nicht wirklich robust. Dafür würde ich `os.path.basename()` verwenden.

Code: Alles auswählen

>>> PDF_EXTENSION
'.pdf'
>>> beispiel
'./beispieldatei.pdf'
>>> os.path.basename(beispiel)[:-len(PDF_EXTENSION) if beispiel.endswith(PDF_EXTENSION) else None]
'beispieldatei'
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ja, das sieht besser aus und ich nehme an, dass TuXX das ".\\" auch mit den ersten drei Zeichen meinte, die er entfernt haben möchte.
BlackJack

Falls zu dem Zeitpunkt das *.pdf keine Rolle mehr spielt, weil generell alle Endungen entfernt werden sollen, oder es sowieso nur noch *.pdf's sind, würde ich es so schreiben (und in eine Funktion verpacken):

Code: Alles auswählen

>>> os.path.splitext(os.path.basename(beispiel))[0]
'beispieldatei'
TuXX
User
Beiträge: 24
Registriert: Sonntag 25. Juni 2017, 17:52

Schrittchen für Schrittchen komme ich einer Lösung näher, allerdings nimmt die Zahl der Fragen nicht ab. Der aktuelle Stand der Software ist folgender:

Code: Alles auswählen

import os
for folderName, subfolders, filenames in os.walk('C:\\Techdok\\'):
    texfile = open ('Verzeichnislist.txt', 'a')
    texfile.write(folderName + "\n")
    for subfolder in subfolders:
        # Abtrennen des letzten Ordners aus dem Pfad
        
        texfile.write(subfolder + "\n")
    for filename in filenames:
        # Trennen des Dateinamens und Dateiendung über die Funktion os.path.splitext. Das Tupel 0 enthält den Dateinamen, während 1 die Endung enthält.
        file = os.path.splitext(filename)[0]
        # Schreiben des Dateinamens ohne Dateiendung
        texfile.write(file + "\n")
    texfile.close()
Trotz dem Studium unzähliger Homepages und Büchern konnte ich bisher keine Lösung dafür finden, wie ich auf einfache Weise den letzten Ordnername von subfolder auslesen kann. Gibt es hierfür überhaupt einen Python-Befehl. Sozusagen einen umgekehrten os.patch.join? Für subfolder wird z.B. c:\Techdok\Ordner1 ausgegeben. Wie kann ich nun Ordner1 extrahieren?
Zuletzt geändert von Anonymous am Mittwoch 5. Juli 2017, 20:11, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@TuXX: es gibt os.path.basename dafür. Die Datei sollte immer noch vor der for-Schleife geöffnet werden.
BlackJack

Wobei das gar nicht sein kann das `subfolder` in dem gezeigten Code mehr als eine Pfadkomponente enthält. Es würde keinen Sinn machen `os.path.basename()` darauf anzuwenden.
TuXX
User
Beiträge: 24
Registriert: Sonntag 25. Juni 2017, 17:52

Die Ausgabe des veröffentlichten Codes ist die Folgende;

C:\Techdok\
Ordner1
Ordner2
Ordner3
C:\Techdok\Ordner1 (hier möchte ich nur Ordner 1 ausgeben!)
Datei1
Datei2
C:\Techdok\Ordner2 (hier möchte ich nur Ordner 2 ausgeben!)
Datei1
C:\Techdok\Ordner3 (hier möchte ich nur Ordner 3 ausgeben!)
Datei1
Datei2
Datei3
Datei4
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Da wurden doch nun bereits mehr Beispiele als genug gezeigt und ehrlich gesagt verstehe ich nicht das Problem. Ob Pfad oder String, alles lässt sich doch zerlegen und gerade habe ich z.B. festgestellt, dass sich relpath auch gut geeignet hätte, um die ersten 3 Zeichen loszuwerden. Mag sein, dass meine Beispiele nicht die besten sind und Schwachstellen haben, doch es wird wohl nun allgemein nicht anders sein, dass man erst einmal eine längere Zeit Lernen muss und Du wirst halt im ersten Jahr auch noch keine fehlerfreie Anwendung schreiben.

Und wenn Du den vierten nicht ausgeben möchtest, warum machst Du es dann, statt einen Tupel oder eine Liste mit gesperrten Verzeichnissen oder Dateien abzufragen?

Code: Alles auswählen

beispiel = ".\\verzeichnis\\beispiel.py"

print("# Ausgabe: {}".format(os.path.splitext(os.path.basename(beispiel))[0]))
print("# Ausgabe: {}".format(os.path.relpath(beispiel)))
print("# Ausgabe: {}".format(beispiel.split("\\")[1]))

# Ausgabe: beispiel
# Ausgabe: verzeichnis\beispiel.py
# Ausgabe: verzeichnis

beispiel = ".\\verzeichnis1\\verzeichnis2\\beispiel.py"

print("# Ausgabe: {}".format(os.path.splitext(os.path.basename(beispiel))[0]))
print("# Ausgabe: {}".format(os.path.relpath(beispiel)))
print("# Ausgabe: {}".format(beispiel.split("\\")[1]))
print("# Ausgabe: {}".format(beispiel.split("\\")[2]))

# Ausgabe: beispiel
# Ausgabe: verzeichnis1\verzeichnis2\beispiel.py
# Ausgabe: verzeichnis1
# Ausgabe: verzeichnis2
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Oben frage ich noch eine einzelne Endung ab.

Code: Alles auswählen

if extension == endung:
In der Zwischenzeit frage ich an dieser Stelle eine Liste mit Endungen ab, um auch zwei oder drei Endungen auswählen und übergeben zu können.

Code: Alles auswählen

if extension in endungsliste:
Und genauso kannst Du das in Verbindung mit not für eine Liste von gesperrten Dateien oder Verzeichnissen machen. Für Verzeichnisse musst Du Dir halt die richtige Stelle suchen, wo Du die Abfrage platzierst.

Code: Alles auswählen

liste = ["datei-31.pdf", "datei-32.pdf", "datei-45.pdf"]

datei = "datei-68.pdf"

if not (datei in liste):
    print("# Ausgabe: Die Datei {} ist nicht in der Liste enthalten.".format(datei))
else:
    print("# Ausgabe: Die Datei {} ist in der Liste enthalten.".format(datei))
    
datei = "datei-32.pdf"

if not (datei in liste):
    print("# Ausgabe: Die Datei {} ist nicht in der Liste enthalten.".format(datei))
else:
    print("# Ausgabe: Die Datei {} ist in der Liste enthalten.".format(datei))


# Ausgabe: Die Datei datei-68.pdf ist nicht in der Liste enthalten.
# Ausgabe: Die Datei datei-32.pdf ist in der Liste enthalten.
BlackJack

@Melewo: Ein paar Anmerkungen zu Deinem Programm (ist etwas lang geworden — nicht erschrecken :-)).

Das Modul lässt sich nicht importieren ohne dass das Programm los läuft und `instanz` ist eine globale Variable. Ersteres verhindert das man das Modul in einer Python-Shell importieren und einzelne Funktionen oder Klassen testen oder in anderen Modulen verwenden kann. Auch Werkzeuge die Informationen per ”introspection” aus dem Modul ziehen, wie Sphinx, das Dokumentationswerkzeug welches viele Python-Projekte verwenden und mit dem auch die Python-Dokumentation erstellt wird, funktionieren nur wenn man Module importieren kann. Ebenso Testwerkzeuge für Unit- und Doctests.

`instanz` ist kein guter Name, denn der ist so generisch das er für wirklich *jeden* Wert in Python geeignet ist. Mich persönlich stört auch, dass es eine falsche Übersetzung für „instance“ ist. Oder wäre, wenn man sich nicht der ”Dummheit” der Masse beugt. :-)

Die Aufteilung des Codes in `Dateisuche` würde ich als falsch ansehen. Ein Objekt sollte nach Ablauf der `__init__()` in einem benutzbaren Zustand sein. Bei `Dateisuche` *muss* man zwingend die `layout()`-Methode aufrufen, wenn man das aber immer zwingend machen muss, dann stellt sich die Frage warum man den Benutzer der Klasse dazu zwingt. Ein guter Grund für eine Zweiteilung ist das die `__init__()` zum Aufrufer zurückkehren sollte, damit der gegebenfalls noch etwas mit dem Objekt machen kann, wenn er möchte. Also in diesem Fall bevor die GUI-Hauptschleife läuft. Aber nach der `__init__()` der Klasse die das Hauptfenster stellt, sollte das Hauptfenster auch komplett sein, soweit es diese Klasse angeht. Das heisst so ziemlich alles aus der `layout()`-Methode sollte im Zuge von `__init__()` passieren. Da die `layout()`-Methode dann nur noch den Aufruf der GUI-Hauptschleife enthält, wäre dafür der Name `mainloop()` passender. Und man könnte sich diese Methode ganz sparen wenn man die Klasse die das Hauptfenster repräsentiert von `Tk` erben lässt, was ja allgemein das Hauptfenster ist und diese Methode bereits besitzt.

Das die `StrinVar`-Objekte mit dem Wert '1' initialisiert werden damit *keine* Auswahl bei den Radiobuttons angezeigt wird ist irreführend, denn wenn der Benutzer *nichts* auswählt, dann werden ja die Werte 'alphabetisch' und 'py' verwendet. Warum wird das dem Benutzer nicht angezeigt? Wenn in der GUI kein Radiobutton ausgewählt ist, dann würde ich als Benutzer ”unsortiert” und Dateien mit beliebiger Endung erwarten.

Hinter `set_auswahl` würde man eine „setter“-Methode erwarten, aber kein `StringVar`-Objekt. Zudem ist das eine Mischung aus Englisch und Deutsch innerhalb eines Namens. `vondate` und `bisdate` ebenfalls, und ohne Trennung zwischen den beiden Namen. Insgesamt sollte man bei *einer* Sprache bleiben um Verwirrungen zu vermeiden. `auswahl` ist auch zu generisch weil der Name nicht verrät *was* da ausgewählt wird — das Sortierkriterium.

`durchlaufe` klingt auch eher nach einer Tätigkeit und kaum jemand würde den Startpfad für die Suche hinter dem Namen vermuten.

`frame_li` und `frame_re` enthalten Abkürzungen und das für ”Ortsangaben” in der GUI die so nicht unbedingt sein müssen. Man könnte sich später auch entscheiden die anders darzustellen, zum Beispiel über- statt nebeneinander, dann stimmen die Namen nicht mehr. Man könnte beide auch einfach nur `frame` nennen, wenn man sie nicht ”auf Vorrat” am Anfang definieren würde, sondern erst wenn man sie benötigt. Oder man verwendet Namen die den Inhalt beschreiben wie `result_frame` und `controls_frame` oder etwas in der Art.

Bei `scrollbr` fehlt ein 'a' — und jetzt sag bitte nicht das das kein Versehen war, sondern das Du diesen einen Konsonanten absichtlich weggelassen hast. ;-)

`drei_buttons` und `sechs_buttons`? Da werden keine `Button`\s dran gebunden und die `drei_*` stimmt noch nicht einmal, denn das Wörterbuch hat vier Einträge. Ausser in der allerneuesten Python 3-Version ist ein Wörterbuch auch keine gute Idee weil die Werte dort keine feste Reihenfolge haben. Die Reihenfolge der Radiobuttons kann also bei jedem Programmstart anders sein. Eine Liste mit Tupeln wäre hier eine sinnvollere Wahl.

`key2` und `wert2` muss auch nicht sein, da kann man `key` und `wert` für schreiben. Nach welchem Kriterium wird entschieden welchen Namen man in Deutsch und welchen in Englisch verwendet? Und wonach ob man Zahlen oder Buchstaben anhängt (`radioba`/`radiobb`)?

`radioba.bind()` und `radiobb.bind()` beziehen sich jeweils nur auf den *letzten* der erzeugten Radiobuttons, das macht keinen Sinn.

Das Erzeugen der Radiobuttons passiert in zwei fast identischen Code-Blöcken. Code- und Datenwiederholung vermeidet man als Programmierer. Programme werden dadurch schwieriger zu warten und damit fehleranfälliger, denn wenn man etwas ändert, muss immer daran denken es in allen Kopien zu ändern und aufpassen das man es überall wirklich gleich ändert.

`uebernehme_auswahl()` sollte nicht lokal definiert werden. Die Funktion ist noch nicht einmal komplett ein Closure weil auf die Hälfte der Werte ”direkt” zugegriffen wird und auf die andere über `self`. Letztlich ist die Funktion aber auch komplett überflüssig, denn warum im Hintergrund Werte übernehmen, wenn man das auch erst dann machen kann wenn man sie *braucht*. Also `von` und `bis` auch an das Objekt binden, und die ganzen redundanten Attribute loswerden und in der `main()` erst alle Werte aus der GUI auslesen.

Der Name `main()` für eine Methode die nicht das Hauptprogramm enthält ist falsch.

Die `suche_files()` ist in gewisser Weise mit der GUI vermischt, denn sie bekommt nahezu ungeprüfte Nutzereingaben. Sie sollte Datumsobjekte bekommen und nicht vom Benutzer eingegebene Zeichenketten. Die Eingabe entgegen zu nehmen zu prüfen und zu konvertieren ist Aufgabe der GUI. Die Daten werden auch ständig aufs neue konvertiert für jeden Vergleich.

Beim Anzeigen des Ergebnisses ist der Name `dateien` falsch weil der nur an einen Dateinamen gebunden wird. Letztlich ist es auch nicht besonders effizient die Namen alle einzeln in das `Text`-Widget zu schreiben und es ist auch ziemlich verwirrend das die in umgekehrter Reihenfolge ausgegeben werden. Ich habe mich zuerst sehr gewundert warum `suche_files()` immer alles `reverse` sortiert bis ich das gesehen habe. Damit rechnet niemand und die Programmlogik sollte nichts über solche Details wissen müssen. Die Funktion formatiert auch die Ergebnisse als Zeichenketten — im Grunde ebenfalls Aufgabe der GUI.

Für die Sortierkriterien würde ich Konstanten für die Zeichenketten einführen, oder noch besser ein `enum.Enum` erstellen.

Das `file_dict` ist ein Fehler wenn nach Zeit sortiert wird, denn Du bildest dort Zeiten auf Dateinamen ab. Das funktioniert nur wenn alle Dateien garantiert unterschiedliche Zeiten haben, was so ganz und gar nicht garantiert ist. Es gibt ``touch`` oder Dateisysteme mit einer relativ niedrigen Zeitauflösung was die Metadaten angeht, da kann man ganz locker viele Dateien mit der gleichen Zeit haben. Es ist auch verwirrend das in manchen Fällen in diesem Wörterbuch Zeiten auf Namen abgebildet werden und in anderen Namen auf Zeiten. Die Ergebniswerte sollten immer die gleiche Reihenfolge haben.

`verzeichnisse` wird nur an *einen* Verzeichnispfad gebunden. Genau so falsch ist `dateinamen` in der inneren Schleife und später `filetimes`.

Es wird (zu) oft `os.path.join()` mit den gleichen Werten gemacht. Also sowohl der Code dafür als auch die tatsächliche Ausführung. Das macht man einmal am Anfang und gut ist.

Das filtern nach Anfangs-/Endzeit ist komisch gelöst. Erstens passiert es nur wenn nicht nach Name sortiert wird. Warum dann nicht? Nichts in der GUI deutet auf diese Einschränkung hin und ich finde das sehr überraschend. Dann müssen entweder beide Endpunkte eingegeben worden sein. Wird nur einer eingegeben, bleibt das Ergebnis einfach leer‽ Auch etwas unerwartet für meinen Geschmack. Man kann doch auch nur eine Grenze setzen wollen und das macht doch auch total Sinn nur nach Dateien zu suchen die älter als X sind oder jünger als Y.

Wenn man das generell für alle Sortierkriterien macht, dann bleibt in den ganzen ``if``/``elif``-Zweigen nur noch das ermitteln von `zeitpunkt` und es unterscheiden sich nur der Test auf die Sortierkonstante und die Funktion die zum ermitteln der Zeit verwendet wird. Das kann man ohne das ``if``/``elif``/… lösen mit einem Wörterbuch das die Sortierkonstanten auf die Funktionen abbildet. Schon hat man nur noch eine Stelle im Code wo Datei + Zeit zur Ergebnisliste hinzugefügt wird.

Anstelle von einer Zahl oder einer Zeichenkette wären Datums-/Zeitangaben besser als `datetime.datetime` repräsentiert. Die kann man auch vergleichen, die `repr()`-Ausgabe ist lesbarer, und man kann die `format()`-Methode verwenden.

So als zwischenergebnis lande ich erst einmal hier (kaum getestet):

Code: Alles auswählen

import os
from datetime import datetime as DateTime
from tkinter import (
    Tk, Frame, Label, Button, Text, Scrollbar, Radiobutton, Entry,
    StringVar, END, LEFT, RIGHT, Y, W
)
# 
# TODO Maybe use `enum.Enum`.
# 
SORT_BY_NAME = 'name'
SORT_BY_CREATION_TIME = 'creation_time'
SORT_BY_CHANGE_TIME = 'change_time'
SORT_BY_ACCESS_TIME = 'access_time'
SORT_BY_TIME = [SORT_BY_CREATION_TIME, SORT_BY_CHANGE_TIME, SORT_BY_ACCESS_TIME]

SORT_BY_TO_GET_TIME_FUNC = {
    SORT_BY_NAME: os.path.getctime,
    SORT_BY_CREATION_TIME: os.path.getctime,
    SORT_BY_CHANGE_TIME: os.path.getmtime,
    SORT_BY_ACCESS_TIME: os.path.getatime,
}

DATE_FORMAT = '%d.%m.%Y'


def search_files(start_path, sort_by, filename_extension, start_date, end_date):
    # 
    # TODO Own class for the result tuples or at least a
    #      `collections.namedtuple`.
    # 
    os.stat_float_times(False)

    result = list()
    for path, _, filenames in os.walk(start_path):
        for filename in filenames:
            full_path = os.path.join(path, filename)
            if os.path.splitext(filename)[1] == filename_extension:
                file_time = DateTime.fromtimestamp(
                    SORT_BY_TO_GET_TIME_FUNC[sort_by](full_path)
                )
                if (
                    (not start_date or start_date < file_time)
                    and (not end_date or file_time <= end_date)
                ):
                    result.append((full_path, file_time))

    if sort_by == SORT_BY_NAME:
        result.sort(key=lambda x: x[0].lower())
    elif sort_by in SORT_BY_TIME:
        result.sort(key=lambda x: x[1])
    else:
        raise ValueError('unknown sort criterion {0!r}'.format(sort_by))

    return result


class Dateisuche(Tk):

    def __init__(self):
        Tk.__init__(self)
        self.title('Dateisuche')
        self['bg'] = 'gray50'

        self.sort_criterion = StringVar(value=SORT_BY_NAME)
        self.filename_extension = StringVar(value='.py')
        self.start_path = '.'
        # 
        # Linker Frame.
        # 
        frame = Frame(self, bg='gray50', padx=2)
        self.result_text = Text(frame, width=92, height=38)
        self.result_text.pack(pady=0, padx=2)
        scrollbar = Scrollbar(frame)
        scrollbar.config(command=self.result_text.yview)
        scrollbar.pack(side=RIGHT, fill=Y)
        self.result_text.config(yscrollcommand=scrollbar.set)
        frame.pack(side=LEFT)
        # 
        # Rechter Frame.
        # 
        self.background_color = '#6f6352'
        self.font_name = 'cambria'
        frame = Frame(self, bg=self.background_color, padx=2)

        self._create_radiobuttons(
            frame,
            'Auswahl Sortierung',
            self.sort_criterion,
            [
                ('Alphabetisch', SORT_BY_NAME),
                ('Datei angelegt', SORT_BY_CREATION_TIME),
                ('Letzte Änderung', SORT_BY_CHANGE_TIME),
                ('Letzter Zugriff', SORT_BY_ACCESS_TIME),
            ],
        )
        self._create_radiobuttons(
            frame,
            'Auswahl Endung',
            self.filename_extension,
            [
                ('doc', '.doc'),
                ('docx', '.docx'),
                ('htm', '.htm'),
                ('html', '.html'),
                ('pdf', '.pdf'),
                ('py', '.py'),
            ],
        )

        self._create_headline(frame, 'Auswahl Zeitraum')
        options = dict(
            bg=self.background_color,
            font=(self.font_name, 10),
            fg='#ffce58',
            justify=LEFT,
        )
        Label(
            frame, text='Beispiel: 28.06.2017\n\nVon:', **options
        ).pack(padx=10, anchor=W)
        self.start_date_entry = Entry(frame)
        self.start_date_entry.pack(padx=10, anchor=W)

        Label(frame, text='Bis:', **options).pack(padx=10, anchor=W)
        self.end_date_entry = Entry(frame)
        self.end_date_entry.pack(padx=10, anchor=W)

        search_button = Button(
            frame,
            text='Suche',
            font=(self.font_name, 10, 'bold'),
            padx=18,
            command=self.do_search,
        )
        search_button.pack(pady=18)

        frame.pack(side=LEFT, fill=Y)

    def _create_headline(self, parent, text):
        Label(
            parent,
            text=text,
            fg='#ffe9b3',
            bg=self.background_color,
            font=(self.font_name, 11),
            justify=LEFT,
        ).pack(padx=10, pady=12, anchor=W)

    def _create_radiobuttons(self, parent, headline_text, variable, choices):
        self._create_headline(parent, headline_text)
        for text, value in choices:
            Radiobutton(
                parent,
                text=text,
                fg='#ffce58',
                bg=self.background_color,
                selectcolor='#606060',
                activebackground=self.background_color,
                variable=variable,
                value=value,
            ).pack(padx=8, anchor=W)

    def do_search(self):
        try:
            start_date = DateTime.strptime(
                self.start_date_entry.get(), DATE_FORMAT
            )
        except ValueError:
            start_date = None

        try:
            end_date = DateTime.strptime(self.end_date_entry.get(), DATE_FORMAT)
        except ValueError:
            end_date = None

        paths = search_files(
            self.start_path,
            self.sort_criterion.get(),
            self.filename_extension.get(),
            start_date,
            end_date,
        )
        self.result_text.delete(1.0, END)
        self.result_text.insert(
            1.0, '\n'.join(map('{0[0]} - {0[1]:%d.%m.%Y}'.format, paths))
        )
        self.result_text.insert(END, '\n')


def main():
    Dateisuche().mainloop()


if __name__ == '__main__':
    main()
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ja, das ist reichlich viel, gehe jetzt nur auf einige Punkte ein und den Rest muss ich erst verarbeiten.
BlackJack hat geschrieben:Das heisst so ziemlich alles aus der `layout()`-Methode sollte im Zuge von `__init__()` passieren.
Gerade in dieser Beziehung hatte ich mir die Bemerkungen von Sirius3 zu Herzen genommen und auf alles was nicht unbedingt zu sein brauchte, unter __init__ verzichtet. Jedenfalls hatte ich es so aufgefassst.
Sirius3 hat geschrieben:Die ganzen GUI-Elemente an Attribute zu binden, ist unnötig. Eigentlich braucht man bei TK außer ein paar IntVar/StrVar-Objekten fast nichts mehr nach der Initialisierung.
viewtopic.php?f=9&t=40640

Dann:
BlackJack hat geschrieben:Das die `StrinVar`-Objekte mit dem Wert '1' initialisiert werden damit *keine* Auswahl bei den Radiobuttons angezeigt wird ist irreführend, denn wenn der Benutzer *nichts* auswählt, dann werden ja die Werte 'alphabetisch' und 'py' verwendet. Warum wird das dem Benutzer nicht angezeigt?
Die werden bei mir richtig angezeigt, vorher wurden alle mit Punkt angezeigt, jetzt nur noch die ausgewählten, deshalb hatte ich doch nach einer Lösung gesucht und die bei Stack Overflow gefunden.
BlackJack hat geschrieben:Bei `scrollbr` fehlt ein 'a' — und jetzt sag bitte nicht das das kein Versehen war, sondern das Du diesen einen Konsonanten absichtlich weggelassen hast.
Beim ersten Script mit der Animation hatte ich das so geschrieben, bei dem hatte ich den Code nur kopiert und nun sehe ich es.
BlackJack hat geschrieben:Das Erzeugen der Radiobuttons passiert in zwei fast identischen Code-Blöcken.
Sind zwischenzeitlich nicht mehr und die unteren wurden ersetzt für eine Mehrfachauswahl mit Checkbuttons für Endungen.

Das ist der Stand von gestern Abend/heute Morgen und außer das a bei scrollbar habe ich jetzt nichts mehr verändert, weil ich mir erst noch einmal alles durchlesen und anschauen möchte, was Du geschrieben und geändert hast.

Code: Alles auswählen

from tkinter import Tk, Frame, Label, Button, Text, Scrollbar, Radiobutton, \
                    Checkbutton, Entry, StringVar, IntVar, END
from tkinter.filedialog import asksaveasfile
import os
import time

# Wandelt Datum 00.00.0000 in Timestamp
def wandle_datum(data):
    return int(time.mktime(time.strptime(data, "%d.%m.%Y")))

# Wandelt Timestamp in Datum 00.00.0000
def wandle_zeitformat(filetimes):
    return time.strftime("%d.%m.%Y", time.localtime(filetimes))

# Formatierung für Listeneintrag, wobei .\\ entfernt wird.
def formatiere_string(files, zeitformat):
    return "{0:s} - ({1:s})".format(os.path.relpath(files), zeitformat)

# Die eigentliche Sortier- und Suchfunktion
def suche_files(durchlaufe, suche, auswahl, endungsliste, vondate, bisdate):
    os.stat_float_times(False)
    file_dict = {}
    suchliste = []
    rueckgabe = []

    # Suche in Dateien und zähle Zeilen mit Treffern
    def zaehle_treffer(file, gesucht, zeit):
        treffer = 0
        with open(file, "r") as datei:
            for zeile in datei:
                if gesucht in zeile:
                    treffer += 1

        if treffer > 0:
            # Einfügen mit Anzahl Treffer - Pfad/Dateiname (Datum) in Liste
            suchliste.append("{0:s} - {1:s} - ({2:s})".format(
                str(treffer), os.path.relpath(file), wandle_zeitformat(zeit)))  

    for verzeichnisse, unterordner, dateien in os.walk(durchlaufe):
        for dateinamen in dateien:
            extension = os.path.splitext(dateinamen)[1]
            if extension in endungsliste:
                pfad = os.path.join(verzeichnisse, dateinamen)
                
                # Ohne Sucheingabe: Pfad/Dateiname als Schlüssel
                if auswahl == "alphabetisch":
                    zeitpunkt = os.path.getctime(pfad)
                    if suche == False:
                        file_dict[pfad] = zeitpunkt
                    else:
                        zaehle_treffer(pfad, suche, zeitpunkt)
                elif auswahl == "angelegt":
                    zeitpunkt = os.path.getctime(pfad)
                elif auswahl == "aenderung":
                    zeitpunkt = os.path.getmtime(pfad)
                elif auswahl == "aufgerufen":
                    zeitpunkt = os.path.getatime(pfad)
                    
                # Ohne Sucheingabe: Timestamp als Schlüssel
                if (auswahl == "angelegt" or auswahl == "aenderung"
                        or auswahl == "aufgerufen"):
                    if vondate == False or bisdate == False:
                        if suche == False:
                            file_dict[zeitpunkt] = pfad
                        else:
                            zaehle_treffer(pfad, suche, zeitpunkt)
                    elif vondate != False and bisdate != False:
                        # Verglichen werden die einzelnen Timestamps
                        if (zeitpunkt >= wandle_datum(vondate) and 
                            zeitpunkt <= wandle_datum(bisdate)):
                            if suche == False:
                                file_dict[zeitpunkt] = pfad
                            else:
                                zaehle_treffer(pfad, suche, zeitpunkt)                            

    # Nur ausführen, falls ein Suchbegriff übergegeben wurde.
    if suche != False:
        rueckgabe = sorted(suchliste, reverse = True)

    # Reihenfolge im Schleifenkopf: files und filetimes
    elif auswahl == "alphabetisch":
        sortiert = sorted(file_dict.items(), key = lambda i: i[0].lower())

        for files, filetimes in sortiert:
            zeitformat = wandle_zeitformat(filetimes)
            rueckgabe.append(formatiere_string(files, zeitformat))

    # Reihenfolge im Schleifenkopf: filetimes und files
    elif (auswahl == "angelegt" or auswahl == "aenderung" 
            or auswahl == "aufgerufen"):
        sortiert = sorted(file_dict.items(), reverse = True)

        for filetimes, files in sortiert:
            zeitformat = wandle_zeitformat(filetimes)
            rueckgabe.append(formatiere_string(files, zeitformat))

    return rueckgabe

class Dateisuche:

    def __init__(self):
        self.fenster = Tk()
        self.durchlaufe = "."            # Startet Durchlauf oder Suche bei "."
        self.endungslis = []             # Liste mit ausgewählten Dateiendungen
        self.sortierung = StringVar(value="1")
        self.textfeld = None
        self.suche_in = None
        self.warnung  = None
        self.von = None
        self.bis = None
        self.auswahl_buttons = {
            "Alphabetisch" : "alphabetisch",
            "Datei angelegt"  : "angelegt",
            "Letzte Änderung" : "aenderung",
            "Letzter Zugriff" : "aufgerufen"
        }
        self.endungen = {
            "doc" : 0,
            "docx": 0,
            "pdf" : 0,
            "txt *" : 0,
            "htm *" : 0,
            "html *": 0,
            "py *"  : 0,
            "pyw *" : 0,
            "php *" : 0
        }

    def main(self):
        hinweise = ""
        # Ein Suchbegriff braucht nicht unbedingt gesetzt zu sein.
        suche_nach = self.suche_in.get()

        if suche_nach is None or suche_nach == "":
            suche_nach = False
            hinweise += "Ein Suchbegriff wurde nicht eingegeben.\n"
        elif len(suche_nach) < 3:
            suche_nach = False
            hinweise += "Der eingegebene Suchbegriff war zu kurz.\n"
        else:
            hinweise += "Suchbegriff: {0:s}\n".format(suche_nach)

        # Sortierungsmethode auf alphabetische oder zeitliche Auswahl pruefen.
        sortmethode = self.sortierung.get()
        sortmethode = ("alphabetisch" if sortmethode == "1" else sortmethode)

        # Nur für Ausgabe der Hinweise
        if suche_nach == False:
            hinweise += "Sortierungsmethode: {0:s} \n".format(sortmethode)
        else:
            hinweise += "Sortierungsmethode: Anzahl Zeilen mit Treffern.\n"

        # Endungsliste leeren, damit bereits ausgwählte Endungen nicht bei
        # jedem weiteren Klick erneut hinzugefügt werden.
        self.endungslis = []
        # Auswahl von Dateiendungen pruefen und notfalls abbrechen.
        for key, value in self.endungen.items():
            status = value.get()
            if status != False:
                endung = ".{0:s}".format(key.replace(" *", ""))
                self.endungslis.append(endung)

        if not self.endungslis:
            self.warnung = True
            warnhinweis  = "Es wurde keine Endung ausgewählt!\n"
        else:
            self.warnung = False
            hinweise += "Ausgewählte Endungen: {}\n".format(self.endungslis)

        # Auf Auswahl eines Zeitraums pruefen.
        vondate = self.von.get()
        bisdate = self.bis.get()

        if vondate is None or vondate == "":
            vondate = False
            vondato = "?"
        else:
            vondato = vondate
        if bisdate is None or bisdate == "":
            bisdate = False
            bisdato = "?"
        else:
            bisdato = bisdate
        # Nur für Ausgabe der Hinweise
        hinweise += "Zeitraum: {0:s} - {0:s}\n".format(vondato, bisdato)

        # Aufruf der Sortier- und Suchfunktion
        if self.warnung == False:
            gefunden = suche_files(self.durchlaufe, suche_nach, sortmethode,
                       self.endungslis, vondate, bisdate)

        # Ausgabe der Hinweise
        self.textfeld.delete(1.0, END)
        if self.warnung == True:
            self.textfeld.insert(1.0, "{0:s}\n".format(warnhinweis))
        else:
            self.textfeld.insert(1.0, "{0:s}\n".format(hinweise))
            # Die von suche_files() zurückgegebene Liste am Ende einfügen.
            for dateien in gefunden:
                self.textfeld.insert(END, "{0:s}\n".format(dateien))

    def speichere_suchergebnis(self):
        datei = asksaveasfile(mode = "a", 
                              filetypes = [("Text Datei", "*.txt")])
        if datei is not None:
            datei.write(self.textfeld.get("1.0", END))
            datei.close()
            self.textfeld.delete("1.0", END)
            self.textfeld.insert(END, "Gespeichert!")
            
    def layout(self):
        self.fenster.title("Dateisuche")
        self.fenster.geometry("980x740")
        self.fenster["bg"] = "#808080"

        # Aufteilung in linken und rechten Frame
        frame_li = Frame(self.fenster, bg = "#808080", padx = 2, pady = 4)
        frame_re = Frame(self.fenster, bg = "#6f6352", padx = 2, pady = 4)

        # Linker Frame:
        # Textfeld für Hinweise, Meldungen und Ergebnisse
        self.textfeld = Text(
                        frame_li, pady = 8, padx = 8, width = 98, height = 46)
        scrollbar = Scrollbar(frame_li)
        scrollbar.config(command = self.textfeld.yview)
        self.textfeld.config(yscrollcommand = scrollbar.set)

        frame_li.pack(side = "left")
        scrollbar.pack(side = "right", fill = "y")
        self.textfeld.pack(pady = 0, padx = 2)

        # Rechter Frame:
        # Suche in mit * gekennzeichneten Dateien
        frame_re.pack(side = "right", fill = "y")
        Label(
            frame_re,
            text = "Suche in *",
            bg = "#6f6352",
            font = ("cambria", 11),
            fg = "#ffe9b3",
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        self.suche_in = Entry(frame_re)
        self.suche_in.pack(padx = 10, anchor = "w")

        # Auswahl Sortierungsmethode
        Label(
            frame_re,
            text = "Auswahl Sortierung",
            bg = "#6f6352",
            font = ("cambria", 11),
            fg = "#ffe9b3",
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        # Sortierungsmethode alphabetisch oder nach zeitlichen Vorgaben
        for key, wert in self.auswahl_buttons.items():
            Radiobutton(
                frame_re, text = key,
                bg = "#6f6352",
                fg = "#ffce58",
                selectcolor = "#606060",
                activebackground = "#6f6352",
                variable = self.sortierung,
                value = wert).pack(padx = 8, anchor = "w")

        # Auswahl von Dateiendungen
        Label(
            frame_re,
            text = "Auswahl Endung",
            bg = "#6f6352",
            font = ("cambria", 11),
            fg = "#ffe9b3",
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        # Auswahl von Dateinamenserweiterungen
        for key in self.endungen:
            self.endungen[key] = IntVar()
            check_endung = Checkbutton(
                frame_re,
                text = key,
                bg = "#6f6352",
                fg = "#ffce58",
                selectcolor = "#606060",
                activebackground = "#6f6352",
                variable=self.endungen[key])
            check_endung.pack(padx = 8, anchor = "w")

        # Auswahl eines Zeitraums
        Label(
            frame_re,
            text = "Auswahl Zeitraum",
            bg = "#6f6352",
            font = ("cambria", 11),
            fg = "#ffe9b3",
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        Label(
            frame_re, 
            text = "Beispiel: 28.06.2017\n\nVon:",
            font = ("cambria", 10), 
            fg = "#ffce58", 
            bg = "#6f6352",
            justify = "left").pack(padx = 10, anchor = "w")
        self.von = Entry(frame_re)
        self.von.pack(padx = 10, anchor = "w")
        Label(
            frame_re,
            text = "Bis:",
            bg = "#6f6352",
            font = ("cambria", 10),
            fg = "#ffce58",
            justify = "left").pack(padx = 10, anchor = "w")
        self.bis = Entry(frame_re)
        self.bis.pack(padx = 10, anchor = "w")

        # Durchlauf mit Sortierung oder Suche starten
        Button(
            frame_re, 
            text = "Suche",
            font = ("cambria", 10, "bold"), padx = 20,
            command = self.main).pack(side = "top", pady = 10)
        # Speichern unter ... 
        Button(
            frame_re, 
            text = "Speichern",
            font = ("cambria", 10, "bold"), padx = 8,
            command = self.speichere_suchergebnis).pack(side = "top")

        self.fenster.mainloop()

if __name__ == "__main__":
    instanz = Dateisuche()
    instanz.layout()
BlackJack

Sirius3 hat im anderen Thema gesagt das nicht alles an das Objekt gebunden werden muss, nicht das es nicht in der `__init__()` passieren sollte.

Zum `StringVar`-Wert '1': Mir ist schon klar was Du da gemacht hast, aber ich bin der Meinung das ist falsch. Denn wenn Du die (oder im neuen das eine) auf '1' setzt dann wird beim starten der GUI *kein* Radiobutton ausgewählt dargestellt. Aber wenn man selber nichts auswählt und auf die Suchen-Fläche klickt, dann wird ja sortiert: alphabetisch. Und darum sollte das auch beim Start des Programm ausgewählt sein, damit der Benutzer das *sieht* das das passiert. Und als die Endungen noch Radiobuttons waren, galt das auch für *.py als Endung — danach wurde gesucht wenn man nichts ausgewählt hat, aber es wurde nicht in der GUI angezeigt.

Jetzt im neuen könntest Du Dir Zeile 145 sparen wenn da nicht mit '1' sondern mit 'alphabetisch' initialisiert werden würde, und der Benutzer würde von Anfang sehen nach was sortiert wird wenn er selber nichts anderes auswählt.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

Ich bin ja noch nicht fertig, werde in den nächsten Tagen noch einmal alles durchgehen und schauen, was sich noch verbessern lässt. Erkenne z.B. keinen zeitlichen Unterschied zwischen getctime und getatime, so dass minimal meine Bezeichnung falsch ist oder vom System abhängig.

Augenblicklich ist es noch so, dass das Script nach dem Start noch nichts ausgibt, also nur ein leeres Textfeld und minimal eine Endung ausgewählt werden muss. Wird der Button ohne Auswahl einer Endung betätigt, wird die Funktion in Zeile 189 nicht aufgerufen und stattdessen in Zeile 195 nur der Hinweis "Es wurde keine Endung ausgewählt!" aus Zeile 165 ins Textfeld geschrieben. Wurde mindestens eine Endung ausgewählt, so ist alles andere optional.
Melewo
User
Beiträge: 320
Registriert: Mittwoch 3. Mai 2017, 16:30

BlackJack hat geschrieben:`frame_li` und `frame_re` enthalten Abkürzungen und das für ”Ortsangaben” in der GUI die so nicht unbedingt sein müssen. Man könnte sich später auch entscheiden die anders darzustellen, zum Beispiel über- statt nebeneinander, dann stimmen die Namen nicht mehr.
Na ja, doch das kann sich eigentlich derjenige umgestalten oder umbenennen, den mein Design nicht passt. Mir gefällt die Aufteilung so wie sie ist, sonst hätte ich das Fenster anders gestaltet.
BlackJack hat geschrieben:Letztlich ist es auch nicht besonders effizient die Namen alle einzeln in das `Text`-Widget zu schreiben
Diesen Punkt habe ich noch geändert, hielt ich es selbst nicht für eine gute Lösung, musste nur erst einmal schauen, wie das besser geschrieben werden könnte und wusste es bis vor wenigen Tagen noch nicht besser. Ich denke, dass es nun besser ist.

Was ich noch geändert habe, wenn ein Suchbegriff mit Umlauten eingegeben wurde, wurden nur Treffer in mit UTF-8 Kodierung gespeicherten Seiten gefunden. nun findet die Treffer in UTF-8 und ISO-Seiten.

Für einen Test das Script in mein htdocs-Verzeichnis auf dem Rechner abgelegt. Windows 10 erzählte mir unter Eigenschaften, dass dieses Verzeichnis 71.378 Dateien in 6.410 Ordner enthalten würde. Gut, die wenigsten davon dürften die Endung *.htm haben, doch ein paar Tausend könnten es auch sein. Jedenfalls um diese 6.410 Ordner zu durchqueren und in allen Dateien mit der Endung *.htm nach dem Suchwort "description" zu suchen, hat die Suche etwa 80 Sekunden benötigt und ich denke, damit kann ich gut leben.

Eigentlich bin ich nun so wie die ist und läuft fürs Erste zufrieden. Einzig, die hätte mir bei der Gelegenheit nicht nur die Seiten mit den meisten Treffern oben listen können, sondern auch die Gesamtzahl der Seiten mit Treffern anzeigen können. Doch darüber mache ich mir vielleicht später noch einmal Gedanken, das kann ja nicht mehr so ein großes Problem sein.

Code: Alles auswählen

from tkinter import Tk, Frame, Label, Button, Text, Scrollbar, Radiobutton, \
                    Checkbutton, Entry, StringVar, IntVar, END
from tkinter.filedialog import asksaveasfile
import time
import os

# Wandelt Datum 00.00.0000 in Timestamp
def wandle_datum(data):
    return int(time.mktime(time.strptime(data, "%d.%m.%Y")))

# Wandelt Timestamp in Datum 00.00.0000
def wandle_zeitformat(filetimes):
    return time.strftime("%d.%m.%Y", time.localtime(filetimes))

# Formatierung für Listeneintrag, wobei .\\ entfernt wird.
def formatiere_string(files, zeitformat):
    return "{0:s} - ({1:s})".format(os.path.relpath(files), zeitformat)

# Umwandlung, falls UTF-8 und ISO-kodierte Dateien mit einem Umlaute 
# enthaltenen Suchbegriff durchsucht werden.
def wandle_umlaute(data):
    umlaute = {
        "\\xc4": "Ä",
        "\\xd6": "Ö",
        "\\xdc": "Ü",
        "\\xe4": "ä",
        "\\xf6": "ö",
        "\\xfc": "ü",
        "\\xdf": "ß"
    }
    for umlaut in umlaute:
        data = data.replace(umlaut, umlaute[umlaut])
    return data.lower()

# Die eigentliche Sortier- und Suchfunktion
def suche_files(durchlaufe, suche, auswahl, endungsliste, vondate, bisdate):
    os.stat_float_times(False)
    file_dict = {}
    suchliste = []
    rueckgabe = []

    # Suche in Dateien und zähle Zeilen mit Treffern
    def zaehle_treffer(file, gesucht, zeit):
        treffer = 0
        with open(file, "r", encoding="utf-8",
                  errors="backslashreplace") as datei:
            for zeile in datei:
                if gesucht.lower() in wandle_umlaute(zeile):
                    treffer += 1

        if treffer > 0:
            # Einfügen mit Anzahl Treffer - Pfad/Dateiname (Datum) in Liste
            suchliste.append("{0:s} - {1:s} - ({2:s})".format(
                str(treffer), os.path.relpath(file), wandle_zeitformat(zeit)))

    for verzeichnisse, unterordner, dateien in os.walk(durchlaufe):
        for dateinamen in dateien:
            extension = os.path.splitext(dateinamen)[1]
            if extension in endungsliste:
                pfad = os.path.join(verzeichnisse, dateinamen)

                # Ohne Sucheingabe: Pfad/Dateiname als Schlüssel
                if auswahl == "alphabetisch":
                    zeitpunkt = os.path.getctime(pfad)
                    if suche == False:
                        file_dict[pfad] = zeitpunkt
                    else:
                        zaehle_treffer(pfad, suche, zeitpunkt)
                elif auswahl == "angelegt":
                    zeitpunkt = os.path.getctime(pfad)
                elif auswahl == "aenderung":
                    zeitpunkt = os.path.getmtime(pfad)
                elif auswahl == "aufgerufen":
                    zeitpunkt = os.path.getatime(pfad)

                # Ohne Sucheingabe: Timestamp als Schlüssel
                if (auswahl == "angelegt" or auswahl == "aenderung"
                        or auswahl == "aufgerufen"):
                    if vondate == False or bisdate == False:
                        if suche == False:
                            file_dict[zeitpunkt] = pfad
                        else:
                            zaehle_treffer(pfad, suche, zeitpunkt)
                    elif vondate != False and bisdate != False:
                        # Verglichen werden die einzelnen Timestamps
                        if (zeitpunkt >= wandle_datum(vondate) and
                            zeitpunkt <= wandle_datum(bisdate)):
                            if suche == False:
                                file_dict[zeitpunkt] = pfad
                            else:
                                zaehle_treffer(pfad, suche, zeitpunkt)

    # Nur ausführen, falls ein Suchbegriff übergegeben wurde.
    if suche != False:
        rueckgabe = sorted(suchliste, reverse = True)

    # Reihenfolge im Schleifenkopf: files und filetimes
    elif auswahl == "alphabetisch":
        sortiert = sorted(file_dict.items(), key = lambda i: i[0].lower())

        for files, filetimes in sortiert:
            zeitformat = wandle_zeitformat(filetimes)
            rueckgabe.append(formatiere_string(files, zeitformat))

    # Reihenfolge im Schleifenkopf: filetimes und files
    elif (auswahl == "angelegt" or auswahl == "aenderung"
            or auswahl == "aufgerufen"):
        sortiert = sorted(file_dict.items(), reverse = True)

        for filetimes, files in sortiert:
            zeitformat = wandle_zeitformat(filetimes)
            rueckgabe.append(formatiere_string(files, zeitformat))

    return rueckgabe

class Dateisuche:

    def __init__(self):
        self.fenster = Tk()
        self.durchlaufe = "."            # Startet Durchlauf oder Suche bei "."
        self.endungslis = []             # Liste mit ausgewählten Dateiendungen
        self.sortierung = StringVar(value="1")
        self.textfeld = None
        self.suche_in = None
        self.warnung  = None
        self.von = None
        self.bis = None
        self.auswahl_buttons = {
            "Alphabetisch": "alphabetisch",
            "Datei angelegt" : "angelegt",
            "Letzte Änderung": "aenderung",
            "Letzter Zugriff": "aufgerufen"
        }
        self.endungen = {
            "doc" : 0,
            "docx": 0,
            "pdf" : 0,
            "txt *" : 0,
            "htm *" : 0,
            "html *": 0,
            "py *"  : 0,
            "pyw *" : 0,
            "php *" : 0
        }

    # Daten von der Eingabe und für Ausgabe prüfen
    def pruefe_daten(self):
        hinweise = ""
        # Ein Suchbegriff braucht nicht unbedingt gesetzt zu sein.
        suche_nach = self.suche_in.get()

        if suche_nach is None or suche_nach == "":
            suche_nach = False
            hinweise += "Ein Suchbegriff wurde nicht eingegeben.\n"
        elif len(suche_nach) < 3:
            suche_nach = False
            hinweise += "Der eingegebene Suchbegriff war zu kurz.\n"
        else:
            hinweise += "Suchbegriff: {0:s}\n".format(suche_nach)

        # Sortierungsmethode auf alphabetische oder zeitliche Auswahl pruefen.
        sortmethode = self.sortierung.get()
        sortmethode = ("alphabetisch" if sortmethode == "1" else sortmethode)

        # Nur für Ausgabe der Hinweise
        if suche_nach == False:
            hinweise += "Sortierungsmethode: {0:s} \n".format(sortmethode)
        else:
            hinweise += "Sortierungsmethode: Anzahl Zeilen mit Treffern.\n"

        # Endungsliste leeren, damit bereits ausgwählte Endungen nicht bei
        # jedem weiteren Klick erneut hinzugefügt werden.
        self.endungslis = []
        # Auswahl von Dateiendungen pruefen und notfalls abbrechen.
        for key, value in self.endungen.items():
            status = value.get()
            if status != False:
                endung = ".{0:s}".format(key.replace(" *", ""))
                self.endungslis.append(endung)

        if not self.endungslis:
            self.warnung = True
            warnhinweis  = "Es wurde keine Endung ausgewählt!\n"
        else:
            self.warnung = False
            hinweise += "Ausgewählte Endungen: {}\n".format(self.endungslis)

        # Auf Auswahl eines Zeitraums pruefen.
        vondate = self.von.get()
        bisdate = self.bis.get()

        if vondate is None or vondate == "":
            vondate = False
            vondato = "?"
        else:
            vondato = vondate
        if bisdate is None or bisdate == "":
            bisdate = False
            bisdato = "?"
        else:
            bisdato = bisdate
        # Nur für Ausgabe der Hinweise
        hinweise += "Zeitraum: {0:s} - {0:s}\n".format(vondato, bisdato)

        # Aufruf der Sortier- und Suchfunktion
        if self.warnung == False:
            gefunden = suche_files(self.durchlaufe, suche_nach, sortmethode,
                       self.endungslis, vondate, bisdate)

        # Ausgabe der Hinweise
        self.textfeld.delete(1.0, END)
        if self.warnung == True:
            self.textfeld.insert(1.0, "{0:s}\n".format(warnhinweis))
        elif gefunden:
            self.textfeld.insert(1.0, "{0:s}\n".format(hinweise))
            # Die von suche_files() zurückgegebene Liste am Ende einfügen.
            self.textfeld.insert(END, "\n".join(map(str, gefunden)))
        else:
            hinweise += "\nEin Fehler trat auf oder " \
                        "es wurden keine Dateien gefunden!"
            self.textfeld.insert(1.0, "{0:s}\n".format(hinweise))

    def speichere_suchergebnis(self):
        datei = asksaveasfile(mode = "a",
                              filetypes = [("Text Datei", "*.txt")])
        if datei is not None:
            datei.write(self.textfeld.get(1.0, END))
            datei.close()
            self.textfeld.delete(1.0, END)
            self.textfeld.insert(END, "Gespeichert!")

    def layout(self):
        self.fenster.title("Meine-Suche")
        self.fenster.geometry("980x740")
        self.fenster["bg"] = "#808080"

        # Aufteilung in linken und rechten Frame
        frame_li = Frame(self.fenster, bg = "#808080", padx = 2, pady = 4)
        frame_re = Frame(self.fenster, bg = "#6f6352", padx = 2, pady = 4)

        # Linker Frame:
        # Textfeld für Hinweise, Meldungen und Ergebnisse
        self.textfeld = Text(
                        frame_li, pady = 8, padx = 8, width = 98, height = 46)
        scrollbar = Scrollbar(frame_li)
        scrollbar.config(command = self.textfeld.yview)
        self.textfeld.config(yscrollcommand = scrollbar.set)
        frame_li.pack(side = "left")
        scrollbar.pack(side = "right", fill = "y")
        self.textfeld.pack(pady = 0, padx = 2)

        # Rechter Frame:
        # Suche in mit * gekennzeichneten Dateien
        frame_re.pack(side = "right", fill = "y")
        Label(
            frame_re,
            text = "Suche in *",
            bg = "#6f6352",
            fg = "#ffe9b3",
            font = ("cambria", 11),
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        self.suche_in = Entry(frame_re)
        self.suche_in.pack(padx = 10, anchor = "w")
        # Auswahl Sortierungsmethode
        Label(
            frame_re,
            text = "Auswahl Sortierung",
            bg = "#6f6352",
            fg = "#ffe9b3",
            font = ("cambria", 11),
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        # Sortierungsmethode alphabetisch oder nach zeitlichen Vorgaben
        for key, wert in self.auswahl_buttons.items():
            Radiobutton(
                frame_re,
                text = key,
                bg = "#6f6352",
                fg = "#ffce58",
                selectcolor = "#606060",
                activebackground = "#6f6352",
                variable = self.sortierung,
                value = wert).pack(padx = 8, anchor = "w")
        # Auswahl von Dateiendungen
        Label(
            frame_re,
            text = "Auswahl Endung",
            bg = "#6f6352",
            fg = "#ffe9b3",
            font = ("cambria", 11),
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        # Auswahl von Dateinamenserweiterungen
        for key in self.endungen:
            self.endungen[key] = IntVar()
            check_endung = Checkbutton(
                frame_re,
                text = key,
                bg = "#6f6352",
                fg = "#ffce58",
                selectcolor = "#606060",
                activebackground = "#6f6352",
                variable=self.endungen[key])
            check_endung.pack(padx = 8, anchor = "w")
        # Auswahl eines Zeitraums
        Label(
            frame_re,
            text = "Auswahl Zeitraum",
            bg = "#6f6352",
            fg = "#ffe9b3",
            font = ("cambria", 11),
            justify = "left").pack(padx = 10, pady = 12, anchor = "w")
        Label(
            frame_re,
            text = "Beispiel: 28.06.2017\n\nVon:",
            bg = "#6f6352",
            fg = "#ffce58",
            font = ("cambria", 10),
            justify = "left").pack(padx = 10, anchor = "w")
        self.von = Entry(frame_re)
        self.von.pack(padx = 10, anchor = "w")
        Label(
            frame_re,
            text = "Bis:",
            bg = "#6f6352",
            fg = "#ffce58",
            font = ("cambria", 10),
            justify = "left").pack(padx = 10, anchor = "w")
        self.bis = Entry(frame_re)
        self.bis.pack(padx = 10, anchor = "w")
        # Durchlauf mit Sortierung oder Suche starten
        Button(
            frame_re,
            text = "Suche",
            font = ("cambria", 10, "bold"), padx = 20,
            command = self.pruefe_daten).pack(side = "top", pady = 10)
        # Speichern unter ...
        Button(
            frame_re,
            text = "Speichern",
            font = ("cambria", 10, "bold"), padx = 8,
            command = self.speichere_suchergebnis).pack(side = "top")

        self.fenster.mainloop()

def main():
    Dateisuche().layout()

if __name__ == "__main__":
    main()
Antworten