Bearbeiten von „Datei search engine.dart“

Zur Navigation springen Zur Suche springen

Warnung: Du bist nicht angemeldet. Deine IP-Adresse wird bei Bearbeitungen öffentlich sichtbar. Melde dich an oder erstelle ein Benutzerkonto, damit Bearbeitungen deinem Benutzernamen zugeordnet werden.

Die Bearbeitung kann rückgängig gemacht werden. Bitte prüfe den Vergleich unten, um sicherzustellen, dass du dies tun möchtest, und veröffentliche dann unten deine Änderungen, um die Bearbeitung rückgängig zu machen.

Aktuelle Version Dein Text
Zeile 9: Zeile 9:
/// [parser]: delivers the description of the program's options
/// [parser]: delivers the description of the program's options
void usage(String message, {ArgParser parser}) {
void usage(String message, {ArgParser parser}) {
   print('''Usage: dgrep [<options>] <pattern> [<file1> [<file2> ...]]
   print('''Usage: dgrep [<options>] <pattern> <file1> [<file2>] ..
   Searches <pattern> in <file1> <file2>...
   Searches <pattern> in <file1> <file2>...
  <fileN>: a directory or a shell file pattern like '*.txt'
    or both like /home/*.txt
<option>:''');
<option>:''');
   if (parser == null) {
   if (parser == null) {
Zeile 26: Zeile 24:
* <code>///</code> markiert einen Kommentar, der automatisch für die Dokumentation ausgewertet wird.
* <code>///</code> markiert einen Kommentar, der automatisch für die Dokumentation ausgewertet wird.
* <code>print(parser.usage);</code> Das Attribut <code>usage</code> der Klasse <code>ArgParser</code> gibt eine automatisch erstellte Beschreibung der Optionen aus.
* <code>print(parser.usage);</code> Das Attribut <code>usage</code> der Klasse <code>ArgParser</code> gibt eine automatisch erstellte Beschreibung der Optionen aus.
* Es wird eine mehrzeiliger Stringkonstante benutzt, die mit drei Apostrophen <code>"'Usage:...<option>:'"</code> eingeschlossen ist. Hinweis: drei Apostrophe sind im Wiki nicht darstellbar, daher die "Simulation" mit Gänsefüßchen und Apostroph.


= Klasse ExitException =
= Klasse ExitException =
Diese Klasse wird als Ausnahme benutzt, um aus einer verschachtelten Situation unkompliziert ans Ende des Programms zu gelangen, wenn bestimmte Bedingungen erfüllt sind.
Diese Klasse wird als Ausnahme benutzt, um aus einer verschachtelten Situation unkompliziert ans Ende des Programms zu gelangen, wenn bestimmte Bedingungen erfüllt sind.
<pre>/// Used for jump out of nested calls.
<pre>class ExitException {
class ExitException {
   final String reason;
   final String reason;
   ExitException(this.reason);
   ExitException(this.reason);
Zeile 39: Zeile 35:
= Die Klasse FileOptions =
= Die Klasse FileOptions =
Diese Klasse speichert die Optionen, die sich auf die Dateiauswahl beziehen.
Diese Klasse speichert die Optionen, die sich auf die Dateiauswahl beziehen.
<pre>/// Stores file selection options.
<pre>class FileOptions {
class FileOptions {
   bool recursive;
   bool recursive;
   RegExp excluded;
   RegExp excluded;
Zeile 63: Zeile 58:
** <code>FileOptions()</code> wird nur in Tests benutzt.
** <code>FileOptions()</code> wird nur in Tests benutzt.
** <code>FileOptions.fromArgument()</code> holt die Optionen aus einem Objekt der Klasse <code>ArgResult</code> aus dem externen Paket <code>args</code>.
** <code>FileOptions.fromArgument()</code> holt die Optionen aus einem Objekt der Klasse <code>ArgResult</code> aus dem externen Paket <code>args</code>.
* <code>recursive = results['recursive'];</code> Das boolsche Attribut <code>recursive</code> wird direkt aus dem Objekt der Klasse <code>ArgResult</code> übernommen. Wir lernen: Zugriff auf die Optionen erfolgt mittels Namen <code>recursive</code>  mit dem eckige-Klammer-Operator wie bei Maps.
* <code>recursive = results['recursive'];</code> Das boolsche Attribut <code>recursive</code> wird direkt aus dem Objekt der Klasse <code>ArgResult</code> übernommen.


= Die Klasse SearchEngine =
= Die Klasse SearchEngine =
Zeile 71: Zeile 66:
Zuerst werden die Attribute definiert:
Zuerst werden die Attribute definiert:
<pre>
<pre>
/// Searches a regular expression in files.
class SearchEngine {
class SearchEngine {
   static bool storeResult = false;
   static bool storeResult = false;
Zeile 89: Zeile 83:
   String rc;
   String rc;
   final lines = <String>[];
   final lines = <String>[];
   static final formatPlaceholders = RegExp(r'%[#fpnFehcl1-9]%');
   final formatPlaceholders = RegExp(r'%[#fpnFehcl1-9]%');
</pre>
</pre>
* Das Attribut <code>formatPlaceholders</code> ist als <code>static</code> definiert, damit findet die Initialiserung nur einmal statt, nicht bei jedem Objekt. Das ist möglich, weil sich der Ausdruck zur Beschreibung eines Platzhalters im Formatstring nie was ändert.


== Der Konstruktor ==
== Der Konstruktor ==
Zeile 105: Zeile 98:
oder <code>below-lines</code>).
oder <code>below-lines</code>).


<pre>/// Creates an output line depending on a given [format].
<pre>String formatLine(
String formatLine(
     String format, String file, String line, int lineNo, String prefix,
     String format, String file, String line, int lineNo, String prefix,
     {RegExpMatch match, int hits}) {
     {RegExpMatch match, int hits}) {
Zeile 124: Zeile 116:
           break;
           break;
         case 'p':
         case 'p':
           line2 = line2.replaceAll(placeholder, dirname(file));
           line2 = line2.replaceAll(placeholder, path.dirname(file));
           break;
           break;
         case 'n':
         case 'n':
           line2 = line2.replaceAll(placeholder, basename(file));
           line2 = line2.replaceAll(placeholder, path.basename(file));
           break;
           break;
         case 'F':
         case 'F':
           line2 = line2.replaceAll(
           line2 = line2.replaceAll(
               placeholder, basenameWithoutExtension(file));
               placeholder, path.basenameWithoutExtension(file));
           break;
           break;
         case 'e':
         case 'e':
           line2 = line2.replaceAll(placeholder, extension(file));
           line2 = line2.replaceAll(placeholder, path.extension(file));
           break;
           break;
         case 'h':
         case 'h':
           line2 = line2.replaceAll(placeholder, match?.group(0) ?? '');
           line2 = line2.replaceAll(placeholder, match?.group(0) ?? '');
           break;
           break;
        case 'c':
      case 'c':
           line2 = line2.replaceAll(placeholder, hits?.toString() ?? '');
           line2 = line2.replaceAll(placeholder, hits?.toString() ?? '');
           break;
           break;
Zeile 176: Zeile 168:
*** <code>line2 = line2.replaceAll(placeholder, lineNo.toString());</code> Es werden alle diese Platzhalter (<code>%#%</code>) durch die Zeilenummer, gewandelt in einen String, ersetzt.
*** <code>line2 = line2.replaceAll(placeholder, lineNo.toString());</code> Es werden alle diese Platzhalter (<code>%#%</code>) durch die Zeilenummer, gewandelt in einen String, ersetzt.
** Nach diesem Schema werden die auch die anderen Platzhalter ersetzt.
** Nach diesem Schema werden die auch die anderen Platzhalter ersetzt.
** <code>match?.group(0) ?? "</code> Der Operator <code>?.</code> sorgt dafür, dass kein Fehler auftritt, wenn das Objekt <code>match</code> vor dem Operator <code>null</code> ist, sondern das Ergebnis von <code>match?.group(0)</code> ist dann <code>null</code>. Der Operator <code>??</code> wird dann aktiv, wenn er Operand vor dem Operator null ist, dann ist das Gesamtergebnis der Operand nach dem Operator, also der Leerstring.
** <code>line2 = line2.replaceAll(placeholder, path.dirname(file));</code>  
* <code>groupNo = placeholder.codeUnitAt(1) - '0'.codeUnitAt(0);</code>
*** <code>path.dirname(file)</code> Am Anfang der Datei steht die Importanweisung <code>import 'package:path/path.dart' as path;</code>
** <code>placeholder.codeUnitAt(1)</code> liefert den Ganzzahlwert des 2.ten Zeichens (gezählt ab 0), von diesem wird der Ganzzahlwert der Ziffer '0' abgezogen. Da die Ganzzahlwerte der Ziffern "hintereinander" liegen, ergibt die Differenz die gewünschte Gruppennummer.
*** Die Formulierung <code>as path</code> definiert einen Zugriffsnamen <code>path</code> (könnte auch <code>blabla</code> heißen, ist aber per Konvention gleichlautend zum Paket.
** Eine verständliche aber inneffizentere Variante wäre:
*** Um auf ein Element (Klasse, Funktion) des Paketes zuzugreifen, ist in diesem Fall immer dieser Zugriffsname notwendig, also <code>path.dirname()</code> ist die Funktion <code>dirname()</code> aus diesem Paket.
** <code>groupNo = int.parse(placeholder[1]);</code>
** Dieses Verfahren dient dazu Namensüberschneidungen (Namensgleichheit in zwei Paketen) eindeutig lösen zu können.


== Methode search() ==
== Methode search() ==
Die Methode bereitet die Suche vor und startet sie.
Die Methode bereitet die Suche vor und startet sie.


<pre>/// Prepares the search and do it.
<pre>void search() {
void search() {
   String exitMessage;
   String exitMessage;
  if (verboseLevel >= 2) {
    print('= current directory: ${Directory.current.path}');
  }
   try {
   try {
     final pattern2 = searchOptions.word ? '\\b$pattern\\b' : pattern;
     final pattern2 = searchOptions.word ? '\\b$pattern\\b' : pattern;
     regExp = RegExp(pattern2, caseSensitive: !searchOptions.ignoreCase);
     regExp = RegExp(pattern2, caseSensitive: !searchOptions.ignoreCase);
     final regExpList = <RegExp>[];
     final regExprList = <RegExp>[];
     final paths = <String>[];
     final paths = <String>[];
     for (var item in filePatterns) {
     for (var item in filePatterns) {
       if (FileSystemEntity.isDirectorySync(item)) {
       if (FileSystemEntity.isDirectorySync(item)) {
         paths.add(item);
         paths.add(item);
         regExpList.add(null);
         regExprList.add(null);
       } else {
       } else {
         final directory = dirname(item);
         var ix = item.lastIndexOf(path.separator);
         final filePattern = basename(item);
         paths.add(ix < 0 ? null : item.substring(0, ix));
         paths.add(item.isEmpty ? '.' : directory);
         final filePattern =
         regExpList.add(filePattern.isEmpty ? null : RegExp(shellPatternToRegExp(filePattern)));
            shellPatternToRegExp(ix < 0 ? item : item.substring(ix + 1));
         regExprList.add(filePattern.isEmpty ? null : RegExp(filePattern));
       }
       }
     }
     }
     try {
     try {
       for (var ix = 0; ix < paths.length; ix++) {
       for (var ix = 0; ix < paths.length; ix++) {
         searchFilePattern(regExpList[ix], paths[ix], 0);
         searchFilePattern(regExprList[ix], paths[ix] ?? '.', 0);
       }
       }
     } on ExitException catch (exc) {
     } on ExitException catch (exc) {
Zeile 237: Zeile 232:
** <code>if (FileSystemEntity.isDirectorySync(item))</code> Test, ob die Angabe nur ein Verzeichnis ist.
** <code>if (FileSystemEntity.isDirectorySync(item))</code> Test, ob die Angabe nur ein Verzeichnis ist.
** Wenn ja, Eintrag in die Pfadliste und <code>null</code> in die Namensmusterliste (für "alle Dateien")
** Wenn ja, Eintrag in die Pfadliste und <code>null</code> in die Namensmusterliste (für "alle Dateien")
** Wenn nein, wird die Angabe in Verzeichnis <code>directory</code> und Dateinamensmuster <code>filePattern</code> zerlegt.
** Wenn nein, wird die Angabe in Pfad und Dateinamensmuster zerlegt, das Muster in einen regulären Ausdruck umgewandelt.
** In die Pfadliste wird das aktuelle Verzeichnis <code>'.'</code> eingetragen, wenn <code>directory</code> leer ist, sonst das Verzeichnis.
*** <code>ix = item.lastIndexOf(path.separator);</code> stellt die Grenze zwischen Pfad und Dateimuster fest: das letzte Auftreten des Pfadtrenners.
* <code>for (var ix = 0; ix < paths.length; ix++)</code> eine Schleife über alle Indizes der Pfadliste.
*** Keine Grenze gefunden (<code>ix < 0</code>): dann gibt es keinen Pfad, in die Pfadliste wird <code>null</code> eingetragen, sonst der vordere Teil, der mit <code>substring(0, ix)</code> ermittelt wird.
* <code>searchFilePattern(regExprList[ix], paths[ix], 0);</code> Diese Methode realisiert die eigentliche Suche.
*** <code>ix < 0 ? item : item.substring(ix + 1)</code> Pfadtrenner nicht vorhanden: der ganze String <code>item</code>, sonst der Teil nach dem Trenner: <code>item.substring(ix + 1)</code>.
* <code>for (var ix = 0; ix < paths.length; ix++)</code> eine Schleife über alle Einträge der Pfadliste
* <code>searchFilePattern(regExprList[ix], paths[ix] ?? '.', 0);</code> Diese Methode realisiert die eigentliche Suche. <code>paths[ix] ?? '.'</code> Nimmt den ix-ten Eintrag von <code>paths</code>, wenn der <code>null</code> ist, dann das aktuelle Verzeichnis, das mit '.' bezeichnet wird.
* <code>try { ... } on ExitException catch (exc)</code> Hier werden die "Schnellabbrüche" aufgefangen. Das passiert, wenn eine Bedingung der Optionen <code>--exit-lines</code> oder <code>--exit-files</code> erfüllt ist.
* <code>try { ... } on ExitException catch (exc)</code> Hier werden die "Schnellabbrüche" aufgefangen. Das passiert, wenn eine Bedingung der Optionen <code>--exit-lines</code> oder <code>--exit-files</code> erfüllt ist.
* <code>try {...} on FormatException catch (exc) {</code> Die Ausnahme wird geworfen, wenn der reguläre Ausdruck in <code>regExp = RegExp(pattern2,...)</code> inkorrekt ist. Es wird dann eine Fehlermeldung mittels der Funktion <code>usage()</code> ausgegeben.
* <code>try {...} on FormatException catch (exc) {</code> Die Ausnahme wird geworfen, wenn der reguläre Ausdruck in <code>regExp = RegExp(pattern2,...)</code> inkorrekt ist. Es wird dann eine Fehlermeldung mittels der Funktion <code>usage()</code> ausgegeben.
Zeile 279: Zeile 276:
           lastShowedLine = lineNo;
           lastShowedLine = lineNo;
         }
         }
        continue;
       } else {
       }
        hitLines++;
      hitLines++;
        totalHitLines++;
      totalHitLines++;
        if (searchOptions.list) {
      if (searchOptions.list) {
          showLine(file);
        showLine(file);
          break;
        break;
        }
      }
        aboveBound = lineNo + searchOptions.aboveContext;
      aboveBound = lineNo + searchOptions.aboveContext;
        if (!searchOptions.count) {
      if (!searchOptions.count) {
          if (searchOptions.belowContext > 0) {
        if (searchOptions.belowContext > 0) {
            for (var lineNo2 = lineNo - searchOptions.belowContext;
          for (var lineNo2 = lineNo - searchOptions.belowContext;
                lineNo2 < lineNo;
              lineNo2 < lineNo;
                lineNo2++) {
              lineNo2++) {
              if (lineNo2 > lastShowedLine) {
            if (lineNo2 > lastShowedLine) {
                line2 = formatLine(searchOptions.formatContext, file,
              line2 = formatLine(searchOptions.formatContext, file,
                    lines[lineNo2 - 1], lineNo2, '<');
                  lines[lineNo2 - 1], lineNo2, '<');
                showLine(line2);
              showLine(line2);
              }
             }
             }
           }
           }
          line2 = formatLine(
              searchOptions.format, file, line, lineNo, prefixMatch,
              match: match);
          showLine(line2);
          lastShowedLine = lineNo;
        }
        if (searchOptions.exitLines != null &&
            totalHitLines >= searchOptions.exitLines) {
          throw ExitException('hit lines: $totalHitLines');
        }
        if (searchOptions.breakLines != null &&
            hitLines >= searchOptions.breakLines) {
          break;
         }
         }
        line2 = formatLine(
            searchOptions.format, file, line, lineNo, prefixMatch,
            match: match);
        showLine(line2);
        lastShowedLine = lineNo;
      }
      if (searchOptions.exitLines != null &&
          totalHitLines >= searchOptions.exitLines) {
        throw ExitException('hit lines: $totalHitLines');
      }
      if (searchOptions.breakLines != null &&
          hitLines >= searchOptions.breakLines) {
        break;
       }
       }
     }
     }
Zeile 325: Zeile 322:
   }
   }
}
}
</pre>
</pre>
* <code>final lines = File(file).readAsLinesSync();</code> Die Klasse <code>File></code> instantiiert ein Objekt mit dem Dateinamen <code>file</code>, die Methode <code>readAsLinesSync()</code> liefert den Dateiinhalt als Liste von Zeilen.
* <code>final lines = File(file).readAsLinesSync();</code> Die Klasse <code>File></code> instantiiert ein Objekt mit dem Dateinamen <code>file</code>, die Methode <code>readAsLinesSync()</code> liefert den Dateiinhalt als Liste von Zeilen.
Zeile 332: Zeile 328:
* <code>isHit = !searchOptions.invertMatch && match != null || searchOptions.invertMatch && match == null;</code>
* <code>isHit = !searchOptions.invertMatch && match != null || searchOptions.invertMatch && match == null;</code>
** Ein Treffer liegt vor, wenn die Option <code>--invert-match</code> gesetzt ist und das Suchmuster nicht gefunden wurde oder wenn  Option <code>--invert-match</code> '''nicht''' gesetzt ist und das Suchmuster gefunden wurde.
** Ein Treffer liegt vor, wenn die Option <code>--invert-match</code> gesetzt ist und das Suchmuster nicht gefunden wurde oder wenn  Option <code>--invert-match</code> '''nicht''' gesetzt ist und das Suchmuster gefunden wurde.
* <code>if (!isHit)</code> Wenn kein Treffer vorliegt, Behandlung des "nachfolgenden Contexts" (Option <code>--above-lines</code>), am Ende ein <code>continue;</code>, damit die Schleife mit dem nächsten Zeile weitermacht.
* <code>if (!isHit)</code> Wenn kein Treffer vorliegt, Behandlung des "nachfolgenden Contexts" (Option <code>--above-lines</code>)
* <code>if (searchOptions.list)</code> Wenn die Option <code>--list</code> gesetzt ist, wird der Dateiname ausgegeben <code>showLine(file);</code> und die Suche in dieser Datei beendet (<code>break</code>).
* <code>if (searchOptions.list)</code> Wenn die Option <code>--list</code> gesetzt ist, wird der Dateiname ausgegeben <code>showLine(file);</code> und die Suche in dieser Datei beendet (<code>break</code>).
* <code>if (!searchOptions.count)</code> Wenn die Option <code>--count</code> nicht gesetzt ist, wird die gefundene Trefferzeile und evt. Umgebungzeilen (Option <code>--below-lines</code>) ausgegeben.
* <code>if (!searchOptions.count)</code> Wenn die Option <code>--count</code> nicht gesetzt ist...
* <code>line2 = formatLine(searchOptions.format, file, line, lineNo, prefixMatch,              match: match);</code> Der Treffer wird aufbereitet ...
* <code>line2 = formatLine(searchOptions.format, file, line, lineNo, prefixMatch,              match: match);</code> Der Treffer wird aufbereitet ...
* <code>showLine(line2);</code> ... und ausgegeben
* <code>showLine(line2);</code> ... und ausgegeben
Zeile 352: Zeile 348:
<pre>/// Searches the files matching the [filePattern] in a [directory].
<pre>/// Searches the files matching the [filePattern] in a [directory].
/// This method is recursive on subdirectories.
/// This method is recursive on subdirectories.
/// [depth] is the nesting level of the recursive calls.
void searchFilePattern(RegExp filePattern, String directory, int depth) {
void searchFilePattern(RegExp filePattern, String directory, int depth) {
   if (verboseLevel >= 2 && depth <= 1 || verboseLevel >= 3) {
   if (verboseLevel >= 2 && depth <= 1 || verboseLevel >= 3) {
Zeile 362: Zeile 357:
     for (var file in Directory(directory).listSync()) {
     for (var file in Directory(directory).listSync()) {
       final name = file.path;
       final name = file.path;
       final node = basename(name);
       final node = path.basename(name);
       if (FileSystemEntity.isDirectorySync(name)) {
       if (FileSystemEntity.isDirectorySync(name)) {
         if (fileOptions.excludedDirs != null &&
         if (fileOptions.excludedDirs != null &&
Zeile 373: Zeile 368:
           subDirectories.add(name);
           subDirectories.add(name);
         }
         }
        continue;
       } else {
       }
        if (filePattern != null && filePattern.firstMatch(node) == null) {
      if (filePattern != null && filePattern.firstMatch(node) == null) {
          ignoredFiles++;
        ignoredFiles++;
          if (verboseLevel >= 4) {
        if (verboseLevel >= 4) {
            print('= ignoring not matching $name');
          print('= ignoring not matching $name');
          }
          continue;
         }
         }
         continue;
         if (fileOptions.excluded != null &&
      }
            fileOptions.excluded.firstMatch(node) != null) {
      if (fileOptions.excluded != null &&
          ignoredFiles++;
          fileOptions.excluded.firstMatch(node) != null) {
          if (verboseLevel >= 4) {
        ignoredFiles++;
            print('= ignoring excluded $name');
        if (verboseLevel >= 4) {
          }
          print('= ignoring excluded $name');
          continue;
         }
         }
         continue;
         if (!fileOptions.processBinaries && isBinary(name)) {
      }
          if (verboseLevel >= 4) {
      if (!fileOptions.processBinaries && isBinary(name)) {
            print('= ignoring binary $name');
        if (verboseLevel >= 4) {
          }
          print('= ignoring binary $name');
          countBinaries++;
          continue;
         }
         }
         countBinaries++;
         final safeHits = totalHitLines;
        continue;
        searchFile(name);
      }
        if (totalHitLines > safeHits) {
      final safeHits = totalHitLines;
          totalHitFiles++;
      searchFile(name);
          if (searchOptions.exitFiles != null &&
      if (totalHitLines > safeHits) {
              totalHitFiles >= searchOptions.exitFiles) {
        totalHitFiles++;
            throw ExitException('hit files: $totalHitFiles');
        if (searchOptions.exitFiles != null &&
          }
            totalHitFiles >= searchOptions.exitFiles) {
          throw ExitException('hit files: $totalHitFiles');
         }
         }
       }
       }
Zeile 423: Zeile 418:
* <code>name = file.path;</code> Das Attribut <code>path</code> liefert den vollen Namen der Datei.
* <code>name = file.path;</code> Das Attribut <code>path</code> liefert den vollen Namen der Datei.
* <code>node = path.basename(name);</code> Die Funktion <code>basename()</code> entfernt die Pfadangabe vom Dateinamen.
* <code>node = path.basename(name);</code> Die Funktion <code>basename()</code> entfernt die Pfadangabe vom Dateinamen.
* <code>if (FileSystemEntity.isDirectorySync(name))</code> Es wird geprüft, ob die Datei ein Verzeichnis ist: Wenn ja, wird untersucht, ob ein Ausschluss des Verzeichnisses mittels der Option <code>--excluded-dirs</code> definiert ist. Wenn nicht, wird der volle Dateiname an die Liste der Unterverzeichnisse angehängt: <code>subDirectories.add(name);</code> und die Schleife mit <code>continue;</code> fortgesetzt, also mit der nächsten Zeile weitergemacht.
* <code>if (FileSystemEntity.isDirectorySync(name))</code> Es wird geprüft, ob die Datei ein Verzeichnis ist: Wenn ja, wird das untersucht, ob ein Ausschluss des Verzeichnisses mittels der Option <code>--excluded-dirs</code> definiert. Wenn nicht, wird der volle Dateiname an die Liste der Untervzeichnisse angehängt: <code>subDirectories.add(name);</code>
* <code>if (filePattern != null && filePattern.firstMatch(node) == null)</code>
* <code>if (filePattern != null && filePattern.firstMatch(node) == null)</code>
** Es wird geprüft, ob ein Dateimuster vorliegt (<code>filePattern != null</code>), aber kein Treffer vorliegt: Dann kommt die Datei nicht in Frage.
** Es wird geprüft, ob ein Dateimuster vorliegt (<code>filePattern != null</code>), aber kein Treffer vorliegt: Dann kommt die Datei nicht in Frage.
Zeile 429: Zeile 424:
* <code>if (fileOptions.excluded != null && fileOptions.excluded.firstMatch(node) != null)</code>
* <code>if (fileOptions.excluded != null && fileOptions.excluded.firstMatch(node) != null)</code>
** Wenn eine Dateiauschlussoption (<code>--excluded</code> definiert ist, und das Suchmuster passt, kommt die Datei nicht in Frage, es geht mit dem nächsten Eintrag weiter (<code>continue</code>).
** Wenn eine Dateiauschlussoption (<code>--excluded</code> definiert ist, und das Suchmuster passt, kommt die Datei nicht in Frage, es geht mit dem nächsten Eintrag weiter (<code>continue</code>).
* <code>if (!fileOptions.processBinaries && isBinary(name))</code>Wenn die Option <code>--process-binary</code> nicht gesetzt ist und die Datei binär ist, geht es zum nächsten Listeneintrag (<code>continue</code>). Die Funktion <code>isBinary</code> stammt aus der [[Datei helper.dart]].
* <code>if (!fileOptions.processBinaries && isBinary(name))</code>Wenn die Option <code>--process-binary</code> nicht gesetzt ist und die Datei binär ist, geht es zum nächsten Listeneintrag (<code>continue</code>).
* <code>searchFile(name);</code> Hier findet die Suche in der Datei statt.
* <code>searchFile(name);</code> Hier findet die Suche in der Datei statt.
* <code>if (totalHitLines > safeHits)</code> Wenn sich die Zahl der Trefferzeilen geändert hat, erhöht sich die Zahl der Trefferdateien. Wenn mit der Option <code>--exit-files</code> hier eine Grenze definiert wurde und diese erreicht ist, wird ein Schnellausstieg mit dem Werfen der Ausnahme <code>ExitException</code> getätigt.
* <code>if (totalHitLines > safeHits)</code> Wenn sich die Zahl der Trefferzeilen geändert hat, erhöht sich die Zahl der Trefferdateien. Wenn mit der Option <code>--exit-files</code> hier eine Grenze definiert wurde und diese erreicht ist, wird ein Schnellausstieg mit dem Werfen der Ausnahme <code>ExitException</code> getätigt.
* <code>try { ... } on FileSystemException {</code> Beim Aufruf der Methode <code>listSync()</code> kann ein Problem auftreten, das das Werfen der Ausnahme <code>FileSystemException</code> auslöst. In diesem Fall wird die Statistik berichtigt und die Suche in diesem Verzeichnis beendet.
* <code>try { ... } on FileSystemException {</code> Beim Aufruf der Methode <code>listSync()</code> kann ein Problem auftreten, das das Werfen der Ausnahme <code>FileSystemException</code> auslöst. In diesem Fall wird die Statistik berichtigt und die Suche in diesem Verzeichnis beendet.
* <code>if (fileOptions.recursive)</code> Wenn die Option <code>--recursive</code> gesetzt ist, wird die Liste der Unterverzeichnisse in einer Schleife <code>for (var subDir in subDirectories)</code> abgearbeitet. Das geschieht durch Aufruf der Methode <code>searchFilePattern()</code>, also einem '''rekursiven Aufruf''' von "sich selber". Die Verschachtelungstiefe <code>depth</code> erhöht sich dann um <code>1</code>.
* <code>if (fileOptions.recursive)</code> Wenn die Option <code>--recursive</code> gesetzt ist, wird die Liste der Unterverzeichnisse in einer Schleife <code>for (var subDir in subDirectories)</code> abgearbeitet. Das geschieht durch Aufruf der Methode <code>searchFilePattern()</code>, also einem '''rekursiven Aufruf''' von "sich selber".


== Die Methode showLine() ==
== Die Methode showLine() ==
Zeile 450: Zeile 445:
Die Methode fügt zum Objekt der Klasse <code>ArgParser</code> aus dem externen Paket <code>args</code> die Beschreibungen der Boolschen Optionen (genannt "Flags") hinzu.
Die Methode fügt zum Objekt der Klasse <code>ArgParser</code> aus dem externen Paket <code>args</code> die Beschreibungen der Boolschen Optionen (genannt "Flags") hinzu.


<pre>// Adds all boolean options to the argument [parser].
<pre>static void addFlags(ArgParser parser) {
static void addFlags(ArgParser parser) {
   parser.addFlag('count',
   parser.addFlag('count',
       abbr: 'c',
       abbr: 'c',
Zeile 465: Zeile 459:
* <code>help: 'Show only the count of lines with hits (per file)',</code> Eine Beschreibung der Option. Wird das Attribut <code>usage</code> der Klasse <code>ArgParser</code> abgefragt, erscheint dieser Hilfetext darin.
* <code>help: 'Show only the count of lines with hits (per file)',</code> Eine Beschreibung der Option. Wird das Attribut <code>usage</code> der Klasse <code>ArgParser</code> abgefragt, erscheint dieser Hilfetext darin.
* <code>negatable: false);</code> Normalerweise kann ein Flag auch in invertierter Form aufgerufen werden, <code>no-count</code> würde den Wert negieren. Das wird mit diesem Parameter <code>negatable</code> ausgeschaltet.
* <code>negatable: false);</code> Normalerweise kann ein Flag auch in invertierter Form aufgerufen werden, <code>no-count</code> würde den Wert negieren. Das wird mit diesem Parameter <code>negatable</code> ausgeschaltet.
* Der Rest der Methode funktioniert nach dem gleichen Schema für alle anderen Boolschen Optionen.
* Der Rest der Methode funktioniert nach dem gleichen Schema.


== Die Methode addOption() ==
== Die Methode addOption() ==
Die Methode fügt zum Objekt der Klasse <code>ArgParser</code> aus dem externen Paket <code>args</code> die Beschreibungen von Optionen hinzu.
Die Methode fügt zum Objekt der Klasse <code>ArgParser</code> aus dem externen Paket <code>args</code> die Beschreibungen von Optionen hinzu.


<pre>// Adds all not boolean options to the argument [parser].
<pre>static void addOptions(ArgParser parser) {
static void addOptions(ArgParser parser) {
   parser.addOption('excluded',
   parser.addOption('excluded',
       abbr: 'x',
       abbr: 'x',
Zeile 482: Zeile 475:
}
}
</pre>
</pre>
* <code>parser.addOption('excluded',</code> definiert eine Option namens <code>excluded</code>. Die Option kann in der Kommandzeile mit <code>--excluded=<string></code> (Wert mit '=' abgetrennt) aufgerufen werden, oder mit <code>--excluded <string></code> (Wert mit Leerzeichen abgetrennt).
* <code>parser.addOption('excluded',</code> definiert eine Option namens <code>excluded</code>. Die Option kann in der Kommandzeile mit <code>--excluded=<string></code> aufgerufen werden, oder mit <code>--excluded <string></code>.
* <code>abbr: 'x',</code> Definiert die Abkürzung, aufzurufen mit <code>-x<string></code> (ohne Trennzeichen) bzw. <code>-x <string></code> (mit Leerzeichen dazwischen).
* <code>abbr: 'x',</code> Definiert die Abkürzung, aufzurufen mit <code>-x<string></code> bzw. <code>-x <string></code>.
* <code>help: 'A regular expression for files to skip, e.g. ".*\.(bak|sic)"');</code> definiert die Beschreibung, die für den Hilfetext verwendet wird.
* <code>help: 'A regular expression for files to skip, e.g. ".*\.(bak|sic)"');</code> definiert die Beschreibung, die für den Hilfetext verwendet wird.
* Die übrigen Optionen werden nach dem gleichen Schema definiert.
* Die übrigen Optionen werden nach dem gleichen Schema definiert.
Zeile 496: Zeile 489:
Das ist eine sinnvolle Designentscheidung.
Das ist eine sinnvolle Designentscheidung.


<pre>/// Executes the total search defined by the program [arguments].
<pre>static SearchEngine execute(List<String> arguments) {
static SearchEngine execute(List<String> arguments) {
   SearchEngine engine;
   SearchEngine engine;
   final parser = ArgParser();
   final parser = ArgParser();
   addFlags(parser);
   addFlags(parser);
   addOptions(parser);
   addOptions(parser);
   try {
   final results = parser.parse(arguments);
    final results = parser.parse(arguments);
  if (results.arguments.length < 2) {
     final intArgs = [
    usage('too few arguments', parser: parser);
      'above-context',
  } else if (results['help']) {
      'context',
     usage(null, parser: parser);
      'below-context',
  } else if (testIntArguments(
      'break-lines',
          results,
      'exit-lines',
          [
      'exit-files',
            'above-context',
      'verbose-level'
            'context',
    ];
            'below-context',
    if (results['help']) {
            'break-lines',
      usage(null, parser: parser);
            'exit-lines',
    } else if (results.rest.isEmpty) {
            'exit-files',
        usage('too few arguments');
            'verbose-level',
    } else if (testIntArguments(results, intArgs, usage) &&
          ],
        testRegExpArguments(results, ['excluded', 'excluded-dirs'], usage)) {
          usage) &&
      if (results.rest.isEmpty) {
      testRegExpArguments(
        usage('too few arguments', parser: parser);
          results,
      } else {
          [
        engine = SearchEngine(results.rest[0],
            'excluded',
            results.rest.length == 1 ? ['.'] : results.rest.sublist(1),
            'excluded-dirs',
            searchOptions: SearchOptions.fromArgument(results),
          ],
            verboseLevel: intValue(results['verbose-level']),
          usage)) {
            fileOptions: FileOptions.fromArgument(results));
    if (results.rest.isEmpty) {
        engine.search();
      usage('too few arguments', parser: parser);
      }
    } else {
      engine = SearchEngine(results.rest[0],
          results.rest.length == 1 ? ['.'] : results.rest.sublist(1),
          searchOptions: SearchOptions.fromArgument(results),
          verboseLevel: intValue(results['verbose-level']),
          fileOptions: FileOptions.fromArgument(results));
      engine.search();
     }
     }
  } on FormatException catch (exc) {
    usage(exc.toString());
   }
   }
   return engine;
   return engine;
Zeile 539: Zeile 535:
* <code>addFlags(parser);</code> und <code>addOptions(parser);</code> fügen die Optionen dazu.
* <code>addFlags(parser);</code> und <code>addOptions(parser);</code> fügen die Optionen dazu.
* <code>results = parser.parse(arguments);</code> Die Verarbeitung der Programmargumente wird erledigt, Ergebnis ist ein Objekt vom Typ <code>ArgResults</code>.
* <code>results = parser.parse(arguments);</code> Die Verarbeitung der Programmargumente wird erledigt, Ergebnis ist ein Objekt vom Typ <code>ArgResults</code>.
* <code>if (results['help'])</code> Wurde die Option <code>--help</code> angegeben? Wenn ja wird die Beschreibung mittels <code>usage(null, parser: parser);</code> ausgegeben, ohne Fehlermeldung (erster Parameter ist <code>null</code>). Wir sehen hier, dass auf die Optionen von <code>results</code> mittels der eckigen Klammern zugegriffen werden kann, wie bei einer Map.
* <code>if (results['help'])</code> Wurde die Option <code>--help</code> benutzt? Wenn ja wird die Beschreibung mittels <code>usage(null, parser: parser);</code> ausgegeben, ohne Fehlermeldung (erster Parameter ist <code>null</code>.
* <code>if (results.rest.isEmpty)</code> Das Attribut <code>rest</code> der Klasse <code>ArgResults</code> enthält alle Programmargumente, die keine Optionen sind, die also nicht mit '-' anfangen. Diese Liste wird getestet, ob sie leer ist. Wenn ja, liegt der Fehler "zu wenig Argumente" vor, der mit der Funktion <code>usage()</code> ausgegeben wird.
* <code>if (testIntArguments(...)</code> Test, ob die Optionen mit Ganzzahlen korrekt belegt wurden. Siehe [[Datei helper.dart]].
* <code>if (testIntArguments(...)</code> Test, ob die Optionen mit Ganzzahlen korrekt belegt wurden. Siehe [[Datei helper.dart]].
* <code>&& testRegExpArguments(...)</code> Test, ob die regulären Ausdrücke korrekt sind. Siehe [[Datei helper.dart]].
* <code>&& testRegExpArguments(...)</code> Test, ob die regulären Ausdrücke korrekt sind. Siehe [[Datei helper.dart]].

Bitte kopiere keine Webseiten, die nicht deine eigenen sind, benutze keine urheberrechtlich geschützten Werke ohne Erlaubnis des Urhebers!
Du gibst uns hiermit deine Zusage, dass du den Text selbst verfasst hast, dass der Text Allgemeingut (public domain) ist, oder dass der Urheber seine Zustimmung gegeben hat. Falls dieser Text bereits woanders veröffentlicht wurde, weise bitte auf der Diskussionsseite darauf hin. Bitte beachte, dass alle Info-Theke-Beiträge automatisch unter der „Gemeinfreiheit“ stehen (siehe Info-Theke:Urheberrechte für Einzelheiten). Falls du nicht möchtest, dass deine Arbeit hier von anderen verändert und verbreitet wird, dann klicke nicht auf „Seite speichern“.

Abbrechen Bearbeitungshilfe (wird in einem neuen Fenster geöffnet)