Datei file supplier.dart

Aus Info-Theke
Zur Navigation springen Zur Suche springen

Klasse ExitException[Bearbeiten]

Diese Klasse wird als Ausnahme benutzt, um aus einer verschachtelten Situation unkompliziert ans Ende des Programms zu gelangen, wenn bestimmte Bedingungen erfüllt sind.

/// Used for jump out of nested calls.
class ExitException {
  final String reason;
  ExitException(this.reason);
}

Die Klasse FileOptions[Bearbeiten]

Diese Klasse speichert die Optionen, die sich auf die Dateiauswahl beziehen.

/// A data class for options to control a [FileSupplier] object.
class FileOptions {
  bool recursive;
  RegExp excluded;
  RegExp excludedDirs;
  bool processBinaries;
  bool yieldFile = true;
  bool yieldDirectory = true;
  bool yieldLinkToFile = true;
  bool yieldLinkToDirectory = true;
  FileOptions({
    this.recursive = false,
    this.excluded,
    this.excludedDirs,
    this.processBinaries = false,
    this.yieldFile = true,
    this.yieldDirectory = true,
    this.yieldLinkToFile = true,
    this.yieldLinkToDirectory = true,
  });
  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'];
    if (results.options.contains('file-type') && results['file-type'] != null) {
      yieldFile =
          yieldDirectory = yieldLinkToFile = yieldLinkToDirectory = false;
      for (var item in results['file-type'].split(',')) {
        if (item.startsWith('f')) {
          yieldFile = true;
        } else if (item.startWith('d')) {
          yieldDirectory = true;
        } else if (item == 'l' || item == 'link') {
          yieldLinkToFile = yieldLinkToDirectory = true;
        } else if (item == 'ld' ||
            item.startsWith('link)') && item.contains('dir')) {
          yieldLinkToDirectory = true;
        } else if (item == 'lf' ||
            item.contains('link') && item.contains('file')) {
          yieldLinkToFile = true;
        }
      }
    }
  }
}
  • Es existieren zwei Konstruktoren:
    • FileOptions() wird nur in Tests benutzt.
    • FileOptions.fromArgument() holt die Optionen aus einem Objekt der Klasse ArgResult aus dem externen Paket args.
  • recursive = results['recursive']; Das boolsche Attribut recursive wird direkt aus dem Objekt der Klasse ArgResult übernommen. Wir lernen: Zugriff auf die Optionen erfolgt mittels Namen recursive mit dem eckige-Klammer-Operator wie bei Maps.

Die Klasse FileSupplier[Bearbeiten]

Die Attribute[Bearbeiten]

/// Implements a generator delivering filenames from one or more file trees.
class FileSupplier {
  final FileOptions fileOptions;
  final int verboseLevel;
  int processedFiles = 0;
  int processedDirs = 0;
  int processedLinksDirectory = 0;
  int processedLinksFile = 0;
  int ignoredFiles = 0;
  int ignoredDirs = 0;
  int countBinaries = 0;
  final List<String> filePatterns;
  final summary = <String>[];
...
}

Der Konstruktor[Bearbeiten]

FileSupplier({this.filePatterns, this.fileOptions, this.verboseLevel = 1});

Die Methode next()[Bearbeiten]

Die Methode implementiert den Generator mit Hilfe einer zweiten Generatorfunktion searchFilePattern().

Iterable<String> next() sync* {
  String exitMessage;
  final paths = <String>[];
  final regExpList = <RegExp>[];
  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));
      regExpList.add(filePattern.isEmpty ? null : RegExp(filePattern));
    }
  }
  try {
    for (var ix = 0; ix < paths.length; ix++) {
      yield* searchFilePattern(regExpList[ix], paths[ix] ?? '.', 0);
    }
  } on ExitException catch (exc) {
    exitMessage = '= search stopped: ' + exc.reason;
  }
  if (verboseLevel >= 1) {
    summary.add(
        '= processed directories: $processedDirs processed files: $processedFiles');
    summary.add(
        '= ignored directories: $ignoredDirs ignored files: $ignoredFiles binary files: $countBinaries');
    summary.add(
        '= directory links (ignored): $processedLinksDirectory file links: $processedLinksFile');
    if (exitMessage != null) {
      summary.add(exitMessage);
    }
  }
}
  • Iterable<String> next() sync* {
    • Durch das sync* weiß der Compiler, dass eine Generatormethode vorliegt, und zwar eine, die eine andere Generatormethode aufruft (wegen des Sterns).
    • Der Ergebnistyp ist Iterable<String>, eine Verallgemeinerung einer Liste:
    • Eine Liste hat ihre Elemente gespeichert, ein Iterator ist eine Menge, auf die sequentiell ("der Reihe nach") zugegriffen werden kann. Beispielsweise nutzt die for-Schleife diesen Datentyp: for (var name in iterator).
  • Wie in der "alten" Methode search werden zwei Listen aufbereitet, die Liste paths mit den Verzeichnisnamen, die Liste regExpList mit Dateisuchmustern.
  • for (var ix = 0; ix < paths.length; ix++) Mit dieser Schleife werden alle Dateimuster abgearbeitet.
  • yield* searchFilePattern(regExpList[ix], paths[ix] ?? '.', 0); yield* liefert den Wert aus, den die folgende Generatormethode searchFilePattern() abliefert.
  • nach der Schleife wird die Statistik noch in der Stringliste summary aufbereitet.

Die Methode searchFilePattern()[Bearbeiten]

Die Methode ist eine Generatormethode, die rekursiv den Dateibaum durchwandert und Dateinamen zurückliefert.

/// Searches the files matching the [filePattern] in a [directory].
/// This method is recursive on subdirectories.
Iterable<String> searchFilePattern(
    RegExp filePattern, String directory, int depth) sync* {
  if (verboseLevel >= 2 && depth <= 1 || verboseLevel >= 3) {
    print('= processing $directory ...');
  }
  final subDirectories = <String>[];
  try {
    processedDirs++;
    for (var file in Directory(directory).listSync()) {
      final name = file.path;
      final node = basename(name);
      if (FileSystemEntity.isDirectorySync(name)) {
        if (FileSystemEntity.isLinkSync(name)) {
          processedLinksDirectory++;
          if (fileOptions.yieldLinkToDirectory) {
            yield name;
          }
          continue;
        }
        if (fileOptions.excludedDirs != null &&
            fileOptions.excludedDirs.firstMatch(node) != null) {
          if (verboseLevel >= 4) {
            print('= ignoring not matching directory $name');
          }
          ignoredDirs++;
        } else {
          if (fileOptions.yieldDirectory) {
            yield name;
          }
          subDirectories.add(name);
        }
        continue;
      }
      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;
      }
      processedFiles++;
      if (FileSystemEntity.isLinkSync(name)) {
        processedLinksFile++;
        if (fileOptions.yieldLinkToFile) {
          yield name;
        }
        continue;
      }
      if (fileOptions.yieldFile) {
        yield name;
      }
    }
  } on FileSystemException catch (exc) {
    processedDirs--;
    ignoredDirs++;
    if (verboseLevel >= 4) {
      print('= ignored: $exc');
    }
  }
  if (fileOptions.recursive) {
    for (var subDir in subDirectories) {
      yield* searchFilePattern(filePattern, subDir, depth + 1);
    }
  }
}
  • Iterable<String> searchFilePattern(...) sync* sync* markiert den Generator, Rückgabetyp ist wieder Iterable.
  • for (var file in Directory(directory).listSync()) Die Schleife iteriert über alle Verzeichniseinträge von directory.
  • if (FileSystemEntity.isDirectorySync(name)) Test, ob die aktuelle Datei ein Verzeichnis ist.
  • if (FileSystemEntity.isLinkSync(name)) Test, ob die aktuelle Datei ein symbolischer Link ist. In diesem Fall liegt ein "Link auf ein Verzeichnis" vor, und der Name darf nicht in der Unterverzeichnisliste landen, da sonst Zyklen möglich sind, die zu Endlosschleifen führen.
  • if (fileOptions.yieldLinkToDirectory) Test, ob "Links auf Verzeichnisse" als Ergebnis gewünscht sind.
  • yield name; Wenn ja, wird der volle Name (mit Pfad) zurückgegeben.
  • In ähnlicher Weise wird auch ein "echtes" Verzeichnis, ein "Link auf eine Datei" und eine "sonstige" Datei zurückgeliefert.
  • if (fileOptions.yieldLinkToDirectory) Wenn die Option --recursive gesetzt ist...
  • for (var subDir in subDirectories) ... wird in dieser Schleife die Funktion rekursiv aufgerufen: yield* searchFilePattern(...).