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> | print('''Usage: dgrep [<options>] <pattern> <file1> [<file2>] .. | ||
Searches <pattern> in <file1> <file2>... | Searches <pattern> in <file1> <file2>... | ||
<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. | ||
= 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> | <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> | <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 | * <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> | ||
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>[]; | ||
final formatPlaceholders = RegExp(r'%[#fpnFehcl1-9]%'); | |||
</pre> | </pre> | ||
== Der Konstruktor == | == Der Konstruktor == | ||
Zeile 105: | Zeile 98: | ||
oder <code>below-lines</code>). | oder <code>below-lines</code>). | ||
<pre> | <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': | |||
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> | ** <code>line2 = line2.replaceAll(placeholder, path.dirname(file));</code> | ||
*** <code>path.dirname(file)</code> Am Anfang der Datei steht die Importanweisung <code>import 'package:path/path.dart' as path;</code> | |||
*** 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. | |||
*** 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. | |||
** | ** 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> | <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 | 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); | ||
regExprList.add(null); | |||
} else { | } else { | ||
var ix = item.lastIndexOf(path.separator); | |||
paths.add(ix < 0 ? null : item.substring(0, ix)); | |||
final 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( | 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 | ** Wenn nein, wird die Angabe in Pfad und Dateinamensmuster zerlegt, das Muster in einen regulären Ausdruck umgewandelt. | ||
** | *** <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 | *** 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; | ||
} | } | ||
} else { | |||
} | hitLines++; | ||
totalHitLines++; | |||
if (searchOptions.list) { | |||
showLine(file); | |||
break; | |||
} | |||
aboveBound = lineNo + searchOptions.aboveContext; | |||
if (!searchOptions.count) { | |||
if (searchOptions.belowContext > 0) { | |||
for (var lineNo2 = lineNo - searchOptions.belowContext; | |||
lineNo2 < lineNo; | |||
lineNo2++) { | |||
if (lineNo2 > lastShowedLine) { | |||
line2 = formatLine(searchOptions.formatContext, file, | |||
lines[lineNo2 - 1], lineNo2, '<'); | |||
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; | |||
} | } | ||
} | } | ||
} | } | ||
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>) | * <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 | * <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. | ||
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); | ||
} | } | ||
} else { | |||
} | if (filePattern != null && filePattern.firstMatch(node) == null) { | ||
ignoredFiles++; | |||
if (verboseLevel >= 4) { | |||
print('= ignoring not matching $name'); | |||
} | |||
continue; | |||
} | } | ||
if (fileOptions.excluded != null && | |||
fileOptions.excluded.firstMatch(node) != null) { | |||
ignoredFiles++; | |||
if (verboseLevel >= 4) { | |||
print('= ignoring excluded $name'); | |||
} | |||
continue; | |||
} | } | ||
if (!fileOptions.processBinaries && isBinary(name)) { | |||
if (verboseLevel >= 4) { | |||
print('= ignoring binary $name'); | |||
} | |||
countBinaries++; | |||
continue; | |||
} | } | ||
final safeHits = totalHitLines; | |||
searchFile(name); | |||
if (totalHitLines > safeHits) { | |||
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 | * <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>) | * <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" | * <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> | <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 | * 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> | <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> | * <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> | * <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> | <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); | ||
final results = parser.parse(arguments); | |||
if (results.arguments.length < 2) { | |||
usage('too few arguments', parser: parser); | |||
} else if (results['help']) { | |||
usage(null, parser: parser); | |||
} else if (testIntArguments( | |||
results, | |||
[ | |||
'above-context', | |||
'context', | |||
'below-context', | |||
'break-lines', | |||
'exit-lines', | |||
'exit-files', | |||
'verbose-level', | |||
], | |||
usage) && | |||
testRegExpArguments( | |||
results, | |||
[ | |||
'excluded', | |||
'excluded-dirs', | |||
], | |||
usage)) { | |||
if (results.rest.isEmpty) { | |||
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(); | |||
} | } | ||
} | } | ||
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> | * <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 (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]]. |