Datei search engine.dart: Unterschied zwischen den Versionen
Zeile 431: | Zeile 431: | ||
== Die Methode showLine() == | == Die Methode showLine() == | ||
Eine übersichtliche Methode, die entweder die übergebenen Zeile in der Klassenvariablen <code>lines</code> speichert (anhängt), was für Unittests benötigt wird, oder die Zeile mittels <code>print()</code> | Eine übersichtliche Methode, die entweder die übergebenen Zeile in der Klassenvariablen <code>lines</code> speichert (anhängt), was für Unittests benötigt wird, oder die Zeile mittels <code>print()</code> ausgibt. | ||
<pre>/// Prints or stores the output line, depending on the option [storeResult]. | <pre>/// Prints or stores the output line, depending on the option [storeResult]. | ||
void showLine(String line) { | void showLine(String line) { | ||
Zeile 441: | Zeile 441: | ||
} | } | ||
</pre> | </pre> | ||
== Die Methode addFlags() == | == Die Methode addFlags() == | ||
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. |
Version vom 4. Januar 2021, 01:13 Uhr
Links
Die Funktion usage()
Die Funktion usage() gibt eine Beschreibung der Benutzung des Programms aus und evt. eine Fehlermeldung.
/// Shows a message how to use the program. /// [message]: null or an error message /// [parser]: delivers the description of the program's options void usage(String message, {ArgParser parser}) { print('''Usage: dgrep [<options>] <pattern> <file1> [<file2>] .. Searches <pattern> in <file1> <file2>... <option>:'''); if (parser == null) { print(' Use -h or --help for more info'); } else { print(parser.usage); } if (message != null) { print('+++ ' + message); } }
///
markiert einen Kommentar, der automatisch für die Dokumentation ausgewertet wird.print(parser.usage);
Das Attributusage
der KlasseArgParser
gibt eine automatisch erstellte Beschreibung der Optionen aus.
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.
class ExitException { final String reason; ExitException(this.reason); }
Die Klasse FileOptions
Diese Klasse speichert die Optionen, die sich auf die Dateiauswahl beziehen.
class FileOptions { bool recursive; RegExp excluded; RegExp excludedDirs; bool processBinaries; FileOptions( {this.recursive = false, this.excluded, this.excludedDirs, this.processBinaries = false}); FileOptions.fromArgument(ArgResults results) { recursive = results['recursive']; excluded = results['excluded'] == null ? null : RegExp(results['excluded']); excludedDirs = results['excluded-dirs'] == null ? null : RegExp(results['excluded-dirs']); processBinaries = results['process-binaries']; } }
- Es existieren zwei Konstruktoren:
FileOptions()
wird nur in Tests benutzt.FileOptions.fromArgument()
holt die Optionen aus einem Objekt der KlasseArgResult
aus dem externen Paketargs
.
recursive = results['recursive'];
Das boolsche Attributrecursive
wird direkt aus dem Objekt der KlasseArgResult
übernommen.
Die Klasse SearchEngine
Die Klasse implementiert ("realisiert") die Suche.
Die Attribute
Zuerst werden die Attribute definiert:
class SearchEngine { static bool storeResult = false; final List<String> filePatterns; final String pattern; final SearchOptions searchOptions; final FileOptions fileOptions; int handledFiles = 0; int handledDirs = 0; int totalHitLines = 0; int totalHitFiles = 0; int ignoredFiles = 0; int ignoredDirs = 0; int countBinaries = 0; int verboseLevel = 4; RegExp regExp; String rc; final lines = <String>[]; final formatPlaceholders = RegExp(r'%[#fpnFehcl1-9]%');
Der Konstruktor
SearchEngine(this.pattern, this.filePatterns, {this.fileOptions, this.searchOptions, this.verboseLevel = 0});
- Es werden das Suchmuster
pattern
, eine Liste von DateimusternfilePatterns
, Dateioptionen, Suchoptionen und ein Level für MeldungenverboseLevel
übergeben und gespeichert.
Die Methode formatLine()
Diese Methode stellt einen String zur Ausgabe zusammen, gesteuert durch ein Format, das per Option definiert wird.
Die notwendigen Daten werden per Parameter übergeben, auch das Format, da es zwei Formate gibt: das für Trefferzeilen und das für Zeilen "der Umgebung" (siehe Option --above-lines
oder below-lines
).
String formatLine( String format, String file, String line, int lineNo, String prefix, {RegExpMatch match, int hits}) { String line2; if (format == null) { line2 = '$prefix$file-$lineNo: $line'; } else { line2 = searchOptions.format; for (var match2 in formatPlaceholders.allMatches(format)) { final placeholder = match2.group(0); switch (placeholder[1]) { case '#': line2 = line2.replaceAll(placeholder, lineNo.toString()); break; case 'f': line2 = line2.replaceAll(placeholder, file); break; case 'p': line2 = line2.replaceAll(placeholder, path.dirname(file)); break; case 'n': line2 = line2.replaceAll(placeholder, path.basename(file)); break; case 'F': line2 = line2.replaceAll( placeholder, path.basenameWithoutExtension(file)); break; case 'e': line2 = line2.replaceAll(placeholder, path.extension(file)); break; case 'h': line2 = line2.replaceAll(placeholder, match?.group(0) ?? ''); break; case 'c': line2 = line2.replaceAll(placeholder, hits?.toString() ?? ''); break; case 'l': line2 = line2.replaceAll(placeholder, line); break; case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': final groupNo = placeholder.codeUnitAt(1) - '0'.codeUnitAt(0); line2 = line2.replaceAll(placeholder, match?.group(groupNo) ?? ''); break; default: break; } } } return line2; }
if (format == null)
Wenn das Formatnull
ist, liegt keine Definition per Option vor, es wird ein Standardformat benutzt, mit Dateiname, Zeilennummer und Zeile.- Ist ein Format definiert:
line2 = searchOptions.format;
Das Format wird kopiert in die Variableline2
kopiert, da das Orginal unverändert bleiben muss.for (var match2 in formatPlaceholders.allMatches(format))
formatPlaceholders
ist ein regulärer Ausdruck, der alle Platzhalter im Format beschreibt:RegExp(r'%[#fpnFehcl1-9]%')
Zwei Prozentzeichen, zwischen denen eines der aufgeführten Zeichen#
...l
oder eine Ziffer1-9
liegt.- Die Klasse
RegExp
liefert mit der MethodeallMatches()
nacheinander alle Treffer des regulären Ausdrucks in dem als Parameter übergebenen String (hierformat
) liefert. Über diese Treffer wird die Schleife gebildet.
final placeholder = match2.group(0);
Der Treffer (beispielsweise%f%
) wird in der Variablenplaceholder
gemerkt.switch(placeholder[1])
Es wird das zweite Zeichen (gezählt wird ab 0) als Fallunterscheidung genommen:case '#':
Ist dieses 2.te Zeichen ein#
...line2 = line2.replaceAll(placeholder, lineNo.toString());
Es werden alle diese Platzhalter (%#%
) durch die Zeilenummer, gewandelt in einen String, ersetzt.
- Nach diesem Schema werden die auch die anderen Platzhalter ersetzt.
line2 = line2.replaceAll(placeholder, path.dirname(file));
path.dirname(file)
Am Anfang der Datei steht die Importanweisungimport 'package:path/path.dart' as path;
- Die Formulierung
as path
definiert einen Zugriffsnamenpath
(könnte auchblabla
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
path.dirname()
ist die Funktiondirname()
aus diesem Paket.
- Dieses Verfahren dient dazu Namensüberschneidungen (Namensgleichheit in zwei Paketen) eindeutig lösen zu können.
Methode search()
Die Methode bereitet die Suche vor und startet sie.
void search() { String exitMessage; if (verboseLevel >= 2) { print('= current directory: ${Directory.current.path}'); } try { final pattern2 = searchOptions.word ? '\\b$pattern\\b' : pattern; regExp = RegExp(pattern2, caseSensitive: !searchOptions.ignoreCase); final regExprList = <RegExp>[]; final paths = <String>[]; for (var item in filePatterns) { if (FileSystemEntity.isDirectorySync(item)) { paths.add(item); regExprList.add(null); } 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 { for (var ix = 0; ix < paths.length; ix++) { searchFilePattern(regExprList[ix], paths[ix] ?? '.', 0); } } on ExitException catch (exc) { exitMessage = '= search stopped: ' + exc.reason; } if (verboseLevel >= 1) { var hits = searchOptions.count || searchOptions.list ? '' : ' matching lines: $totalHitLines'; print( '= processed directories: $handledDirs processed files: $handledFiles$hits'); print( '= ignored directories: $ignoredDirs ignored files: $ignoredFiles binary files: $countBinaries'); if (exitMessage != null) { print(exitMessage); } } } on FormatException catch (exc) { usage('error in regular expression "$pattern": $exc'); } }
final pattern2 = searchOptions.word ? '\\b$pattern\\b' : pattern;
- Bedingter Ausdruck:
- Wenn die Option
--word
gesetzt wurde, wird an das Suchmuster ein\b
vorangestellt und angehängt: dieses Metazeichen steht für nicht für einen String, sondern für eine Wortgrenze, also genau das was wir hier brauchen. - Ohne die Word-Option wird das Muster
pattern
direkt verwendet.
regExp = RegExp(pattern2, caseSensitive: !searchOptions.ignoreCase);
- Die Option
--ignore-case
wird hier berücksichtigt. Da case-sensitiv die logische Umkehrung von ignore-case ist, wird der logische Operator!
eingesetzt, der austrue
false
macht und umgekehrt.
- Die Option
for (var item in filePatterns)
Die Dateinamensmuster können Pfade enthalten oder nicht, diese Info wird in dieser Schleife ermittelt, in Form von zwei Listen mit Pfadenpaths
und mit DateimusterregExprList
if (FileSystemEntity.isDirectorySync(item))
Test, ob die Angabe nur ein Verzeichnis ist.- Wenn ja, Eintrag in die Pfadliste und
null
in die Namensmusterliste (für "alle Dateien") - Wenn nein, wird die Angabe in Pfad und Dateinamensmuster zerlegt, das Muster in einen regulären Ausdruck umgewandelt.
ix = item.lastIndexOf(path.separator);
stellt die Grenze zwischen Pfad und Dateimuster fest: das letzte Auftreten des Pfadtrenners.- Keine Grenze gefunden (
ix < 0
): dann gibt es keinen Pfad, in die Pfadliste wirdnull
eingetragen, sonst der vordere Teil, der mitsubstring(0, ix)
ermittelt wird. ix < 0 ? item : item.substring(ix + 1)
Pfadtrenner nicht vorhanden: der ganze Stringitem
, sonst der Teil nach dem Trenner:item.substring(ix + 1)
.
for (var ix = 0; ix < paths.length; ix++)
eine Schleife über alle Einträge der PfadlistesearchFilePattern(regExprList[ix], paths[ix] ?? '.', 0);
Diese Methode realisiert die eigentliche Suche.paths[ix] ?? '.'
Nimmt den ix-ten Eintrag vonpaths
, wenn dernull
ist, dann das aktuelle Verzeichnis, das mit '.' bezeichnet wird.try { ... } on ExitException catch (exc)
Hier werden die "Schnellabbrüche" aufgefangen. Das passiert, wenn eine Bedingung der Optionen--exit-lines
oder--exit-files
erfüllt ist.try {...} on FormatException catch (exc) {
Die Ausnahme wird geworfen, wenn der reguläre Ausdruck inregExp = RegExp(pattern2,...)
inkorrekt ist. Es wird dann eine Fehlermeldung mittels der Funktionusage()
ausgegeben.
Die Methode searchFile()
Die Methode durchsucht eine Datei nach dem spezifizierten Suchstring. Sie ist relativ umfangreich, was an den vielen Suchoptionen liegt.
/// Read the [file]'s content and search for the search pattern respecting /// the search options. /// Uses [showLine()] to show the matching lines. void searchFile(String file) { if (verboseLevel >= 3) { print('= processing $file ...'); } try { final lines = File(file).readAsLinesSync(); handledFiles++; var hitLines = 0; var lineNo = 0; var lastShowedLine = 0; var aboveBound = 0; var line2; final prefixMatch = searchOptions.belowContext > 0 || searchOptions.aboveContext > 0 ? '=' : ''; for (var line in lines) { lineNo++; final match = regExp.firstMatch(line); final isHit = !searchOptions.invertMatch && match != null || searchOptions.invertMatch && match == null; if (!isHit) { if (lineNo <= aboveBound) { line2 = formatLine( searchOptions.formatContext, file, line, lineNo, '>'); showLine(line2); 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; } } } if (searchOptions.count) { showLine(searchOptions.format == null ? '$hitLines $file' : formatLine(searchOptions.format, file, '', 0, '', hits: hitLines)); } } on FileSystemException { ignoredFiles++; } }
final lines = File(file).readAsLinesSync();
Die KlasseFile>
instantiiert ein Objekt mit dem Dateinamenfile
, die MethodereadAsLinesSync()
liefert den Dateiinhalt als Liste von Zeilen.for (var line in lines)
iteriert über alle Zeilenfinal match = regExp.firstMatch(line);
Test, ob die Zeileline
das Suchmuster enthält.isHit = !searchOptions.invertMatch && match != null || searchOptions.invertMatch && match == null;
- Ein Treffer liegt vor, wenn die Option
--invert-match
gesetzt ist und das Suchmuster nicht gefunden wurde oder wenn Option--invert-match
nicht gesetzt ist und das Suchmuster gefunden wurde.
- Ein Treffer liegt vor, wenn die Option
if (!isHit)
Wenn kein Treffer vorliegt, Behandlung des "nachfolgenden Contexts" (Option--above-lines
)if (searchOptions.list)
Wenn die Option--list
gesetzt ist, wird der Dateiname ausgegebenshowLine(file);
und die Suche in dieser Datei beendet (break
).if (!searchOptions.count)
Wenn die Option--count
nicht gesetzt ist...line2 = formatLine(searchOptions.format, file, line, lineNo, prefixMatch, match: match);
Der Treffer wird aufbereitet ...showLine(line2);
... und ausgegebenif (searchOptions.exitLines != null && totalHitLines >= searchOptions.exitLines)
Wenn die Option--exit-lines
gesetzt ist und die Zahl der Trefferzeilen erreicht ist, Schnellabbruch mit der AusnahmeExitException
.if (searchOptions.breakLines != null && hitLines >= searchOptions.breakLines)
Wenn die Option--break-lines
gesetzt ist und die Zahl der Trefferzeilen in der Datei erreicht ist, wird die Suchschleife beendet (break
).if (searchOptions.count)
Wenn die Option--count
gesetzt ist, wird die Anzahl der Treffer in dieser Datei ausgegeben (nach der Suchschleife, wenn die Treffer gezählt sind).try { ... } on FileSystemException {
Beim Lesen der Datei kann ein Problem auftreten, beispielsweise ein Rechteproblem, es wird dann die AusnahmeFileSystemException
geworfen. Wir zählen das Problem, fertig.
Die Methode searchFilePattern()
Die Methode sucht die Dateien gemäß den Dateioptionen aus dem Dateibaum.
Die Methode ist rekursiv, das heißt sie ruft sich selber auf: Sie durchsucht ein Verzeichnis,
das mit dem Parameter directory
spezifiziert ist, auf passende Dateien und ermittelt nebenbei alle Unterverzeichnisse.
Danach ist eine Suche in den Unterverzeichnissen notwendig, und genau das kann die Methode, sie muss
sich nur mit einem anderen Parameter directory
aufrufen.
/// Searches the files matching the [filePattern] in a [directory]. /// This method is recursive on subdirectories. void searchFilePattern(RegExp filePattern, String directory, int depth) { if (verboseLevel >= 2 && depth <= 1 || verboseLevel >= 3) { print('= processing $directory ...'); } final subDirectories = <String>[]; try { handledDirs++; for (var file in Directory(directory).listSync()) { final name = file.path; final node = path.basename(name); if (FileSystemEntity.isDirectorySync(name)) { if (fileOptions.excludedDirs != null && fileOptions.excludedDirs.firstMatch(node) != null) { if (verboseLevel >= 4) { print('= ignoring not matching directory $name'); } ignoredDirs++; } else { 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'); } } } } } on FileSystemException { handledDirs--; ignoredDirs++; } if (fileOptions.recursive) { for (var subDir in subDirectories) { searchFilePattern(filePattern, subDir, depth + 1); } } }
final subDirectories = <String>[];
Eine leere Liste für die Unterverzeichnisse wird angelegt.for (var file in Directory(directory).listSync())
- Ein Objekt der Klasse
Directory
wird instantiiert, mit dem Verzeichnisnamen als Parameter. Dieses Objekt liefert mit der MethodelistSync()
eine Liste vonFile
-Objekten ab, die mit der Schleife abgearbeitet wird.
- Ein Objekt der Klasse
name = file.path;
Das Attributpath
liefert den vollen Namen der Datei.node = path.basename(name);
Die Funktionbasename()
entfernt die Pfadangabe vom Dateinamen.if (FileSystemEntity.isDirectorySync(name))
Es wird geprüft, ob die Datei ein Verzeichnis ist: Wenn ja, wird das untersucht, ob ein Ausschluss des Verzeichnisses mittels der Option--excluded-dirs
definiert. Wenn nicht, wird der volle Dateiname an die Liste der Untervzeichnisse angehängt:subDirectories.add(name);
if (filePattern != null && filePattern.firstMatch(node) == null)
- Es wird geprüft, ob ein Dateimuster vorliegt (
filePattern != null
), aber kein Treffer vorliegt: Dann kommt die Datei nicht in Frage. continue;
es geht mit dem nächsten Eintrag aus derFile
-Liste weiter.
- Es wird geprüft, ob ein Dateimuster vorliegt (
if (fileOptions.excluded != null && fileOptions.excluded.firstMatch(node) != null)
- Wenn eine Dateiauschlussoption (
--excluded
definiert ist, und das Suchmuster passt, kommt die Datei nicht in Frage, es geht mit dem nächsten Eintrag weiter (continue
).
- Wenn eine Dateiauschlussoption (
if (!fileOptions.processBinaries && isBinary(name))
Wenn die Option--process-binary
nicht gesetzt ist und die Datei binär ist, geht es zum nächsten Listeneintrag (continue
).searchFile(name);
Hier findet die Suche in der Datei statt.if (totalHitLines > safeHits)
Wenn sich die Zahl der Trefferzeilen geändert hat, erhöht sich die Zahl der Trefferdateien. Wenn mit der Option--exit-files
hier eine Grenze definiert wurde und diese erreicht ist, wird ein Schnellausstieg mit dem Werfen der AusnahmeExitException
getätigt.try { ... } on FileSystemException {
Beim Aufruf der MethodelistSync()
kann ein Problem auftreten, das das Werfen der AusnahmeFileSystemException
auslöst. In diesem Fall wird die Statistik berichtigt und die Suche in diesem Verzeichnis beendet.if (fileOptions.recursive)
Wenn die Option--recursive
gesetzt ist, wird die Liste der Unterverzeichnisse in einer Schleifefor (var subDir in subDirectories)
abgearbeitet. Das geschieht durch Aufruf der MethodesearchFilePattern()
, also einem rekursiven Aufruf von "sich selber".
Die Methode showLine()
Eine übersichtliche Methode, die entweder die übergebenen Zeile in der Klassenvariablen lines
speichert (anhängt), was für Unittests benötigt wird, oder die Zeile mittels print()
ausgibt.
/// Prints or stores the output line, depending on the option [storeResult]. void showLine(String line) { if (storeResult) { lines.add(line); } else { print(line); } }
Die Methode addFlags()
Die Methode fügt zum Objekt der Klasse ArgParser
aus dem externen Paket args
die Beschreibungen der Boolschen Optionen (genannt "Flags") hinzu.
static void addFlags(ArgParser parser) { parser.addFlag('count', abbr: 'c', help: 'Show only the count of lines with hits (per file)', negatable: false); parser.addFlag('ignore-case', abbr: 'i', help: 'The search is case insensitive', negatable: false); ... } * <code>parser.addFlag('count',</code> Hinzufügen eines Optionsflags namens <code>count</code>, das bedeutet, die Option wird mit <code>--count</code> in der Kommandozeile aufgerufen. * <code>abbr: 'c',</code> Es gibt eine abkürzende Notierung, nämlich <code>-c</code>. * <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</count> würde den Wert negieren. Das wird mit diesem Parameter <code>negatable</code> ausgeschaltet. * Der Rest der Methode funktioniert nach dem gleichen Schema. == 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. <pre>static void addOptions(ArgParser parser) { parser.addOption('excluded', abbr: 'x', help: 'A regular expression for files to skip, e.g. ".*\.(bak|sic)"'); parser.addOption('excluded-dirs', abbr: 'X', help: 'A regular expression for directory to skip, e.g. "test|\.git|\.config'); ... }
parser.addOption('excluded',
definiert eine Option namensexcluded
. Die Option kann in der Kommandzeile mit--excluded=<string>
aufgerufen werden, oder mit--excluded <string>
.abbr: 'x',
Definiert die Abkürzung, aufzurufen mit-x<string>
bzw.-x <string>
.help: 'A regular expression for files to skip, e.g. ".*\.(bak|sic)"');
definiert die Beschreibung, die für den Hilfetext verwendet wird.- Die übrigen Optionen werden nach dem gleichen Schema definiert.
Die Methode
Diese statische Methode kann in main()
aufgerufen werden, sie übernimmt die
Programmargumente mit dem Parameter arguments
.
Als Ergebnis wird ein Objekt der Klasse SearchEngine
zurückgegeben, das wird ausgiebig bei den Unittests genutzt, die die Attribute der Klasse dann untersuchen.
Durch diese Konstruktion können die Tests mit größtmöglicher Nähe zum "normalen" Ablauf ablaufen. Das ist eine sinnvolle Designentscheidung.
static SearchEngine execute(List<String> arguments) { SearchEngine engine; final parser = ArgParser(); addFlags(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; }
final parser = ArgParser();
Initialisierung der Verarbeitung von Programmargumenten.addFlags(parser);
undaddOptions(parser);
fügen die Optionen dazu.results = parser.parse(arguments);
Die Verarbeitung der Programmargumente wird erledigt, Ergebnis ist ein Objekt vom TypArgResults
.if (results['help'])
Wurde die Option--help
benutzt? Wenn ja wird die Beschreibung mittelsusage(null, parser: parser);
ausgegeben, ohne Fehlermeldung (erster Parameter istnull
.if (testIntArguments(...)
Test, ob die Optionen mit Ganzzahlen korrekt belegt wurden.
Siehe Datei helper.dart.
&& testRegExpArguments(...)
Test, ob die regulären Ausdrücke korrekt sind. Siehe Datei helper.dart.engine = SearchEngine(...)
Die Suchverarbeitung wird initialisiert...engine.search()
... und durchgeführt.return engine;/code> Das Ergebnis ist das Objekt mit den Suchergebnissen.
Die Klasse SearchOptions
Diese Klasse speichert die Optionen, die sich auf die Textsuche beziehen.
class SearchOptions {
bool count;
bool ignoreCase;
bool invertMatch;
bool list;
bool word;
String format;
String formatContext;
int aboveContext;
int belowContext;
int breakLines;
int exitLines;
int exitFiles;
SearchOptions(
{this.count = false,
this.ignoreCase = false,
this.invertMatch = false,
this.list = false,
this.word = false,
this.format,
this.formatContext,
this.aboveContext = 0,
this.belowContext = 0,
this.exitFiles,
this.breakLines,
this.exitLines});
SearchOptions.fromArgument(ArgResults results) {
count = results['count'];
ignoreCase = results['ignore-case'];
invertMatch = results['invert-match'];
list = results['list'];
word = results['word'];
format = results['format'];
formatContext = results['format-context'];
aboveContext = intValue(results['above-context']);
belowContext = intValue(results['below-context']);
final context = intValue(results['context']);
if (context > 0) {
aboveContext = belowContext = context;
}
breakLines = intValue(results['break-lines']);
exitLines = intValue(results['exit-lines']);
exitFiles = intValue(results['exit-files']);
}
}