Datei helper.dart
Links
Zielsetzung
Die Datei helper.dart
enthält Funktionen, die wiederverwendbar sind.
Import
import 'dart:io'; import 'package:args/args.dart'; import 'package:path/path.dart';
- Es werden also ein "internes" Paket
dart:io
und zwei externe Pakete eingebunden: args und path
Die Funktion isBinary()
Test, ob eine Datei eine Binärdatei ist.
Algorithmus: Untersuche die ersten 4 kByte der Datei, ob dort mindestens ein Nullbyte vorkommt oder die Anzahl der Kontrollzeichen (ASCII-Code < Leerzeichen und keine Tabulator oder Zeilenendenzeichen) über 25% ist.
/// Tests whether a file named [filename] is a binary file. /// Returns true if ASCII control characters lower than ' ' are greater than 25% bool isBinary(String filename) { final file = File(filename); var rc = false; if (file.existsSync()) { final handle = file.openSync(); final buffer = handle.readSync(4096); handle.close(); var countControl = 0; for (var ix = 0; ix < buffer.lengthInBytes; ix++) { final byte = buffer[ix]; if (byte == 0) { rc = true; break; } if (byte < 8 /* TAB */ || byte > 13 /* LF */ && byte < 32 /* ' '*/) { countControl++; } } rc = rc || countControl * 4 > buffer.lengthInBytes; } return rc; }
/// Tests ...
Eine kurze Beschreibung der Funktion. Hinweis: Kommentare mit 3 Schrägstrichen///
werden automatisch in die Dokumentation übernommen. Verweise in den Code werden mit eckigen Klammern angezeigt, z.B. [filename] besagt, dassfilename
ein Parameter ist.bool isBinary(String filename) {
Die Funktion hat als Ergebnis einen Wahrheitswert (bool) und als Parameter einen String, der den vollen Dateinamen angibt.file = File(filename);
- Im Paket "dart:io" gibt es die Klasse
File
, die im Konstruktor den Dateinamen bekommt. - Mit einem Objekt dieser Klasse können viele Dateioperationen erledigt werden.
- Im Paket "dart:io" gibt es die Klasse
if (file.existsSync()) {
Es wird geprüft, ob die Datei existiert. Hinweis: dasSync
im Namen bedeutet, dass die Methode synchron ausgeführt wir, also sofort. Im nächsten Kapitel werden im Gegensatz dazu asynchrone Funktionen erklärt.- Wenn nein, passiert nichts, die Variable
rc
hat dann den Wertfalse
:- Wenn die Datei existiert:
handle = file.openSync();
Die Datei wird geöffnet, kein Parameter bedeutet, sie wird zum Lesen geöffnet.buffer = handle.readSync(4096);
Es werden bis zu 4096 Bytes gelesen, dieser Inhalt landet in der Variablebuffer
.handle.close();
Wichtig: Wenn eine Datei geöffnet wird, muss sie auch wieder geschlossen werden, sonst werden Resourcen wie Arbeitsspeicher nicht wieder freigegeben.for (var ix = 0; ix < buffer.lengthInBytes; ix++) {
Wir gehen in einer Schleife alle Bytes des bis zu 4-kByte Blockes durch:byte = buffer[ix]
Wir merken uns das aktuelle Byte.if (byte == 0)
Hat das aktuelle Byte den Wert 0 (dieser Wert kommt in Textdateien nie vor).rc = true;
Der Rückgabewert isttrue
, die Datei ist binär.break
Wir brechen die Schleife ab.if (byte < 8 /* TAB */ || byte > 13 /* LF */ && byte < 32 /* ' '*/) {
- Es wird geprüft, ob ein in Texten selten vorkommendes Zeichen vorliegt
countControl++;
Wenn ja, zählen wir dieses Controlzeichen
rc = rc || countControl * 4 > buffer.lengthInBytes;
- Wir befinden uns hinter der for-Schleife.
- Es findet eine boolsche Operation mit dem Operator
||
statt: - Wenn
rc
den Werttrue
hat, dann wurde ein Nullbyte gefunden, das Ergebnis isttrue
, denn: (true || irgendwas) ist true - Wenn
rc
false ist (also kein Nullbyte gefunden wurde), dann wird geprüft, ob die Anzahl der Kontrollzeichen mehr als ein Viertel (also 25%) der Gesamtzahl ist. Wenn ja, ist das Ergebnistrue
, die Datei ist eine Binärdatei.
Die Funktion intValue()
Wandelt einen String in eine Ganzzahl unter Berücksichtigung von null
.
/// Converts a string [value] into an int. /// Returns null on null or invalid numbers. int intValue(String value){ return value == null ? null : int.tryParse(value); }
- Wenn der Parameter
value
null
ist, ist das Ergebnis null. - Wenn nicht, wird aus dem String eine Zahl, wobei
int.tryParse()
auch null liefert, wenn der String keine Zahl ist, ansonsten die entsprechende Ganzzahl.
Die Funktion regExprEscape()
/// Escapes all meta characters in [string] for a regular expression string. /// Returns [string] with all meta characters escaped by a preceding backslash. /// Note: the algorithm is taken from the Python standard library. String regExprEscape(String string) { String rc; if (string != null) { rc = ''; for (var ii = 0; ii < string.length; ii++) { final cc = string[ii]; if ('()[]{}?*+-|^\$\\.&~# \t\n'.contains(cc)) { rc += r'\'; } rc += cc; } } return rc; }
- Die Funktion wandelt alle Zeichen, die in einem regulären Ausdruck eine Sonderbedeutung haben (Metazeichen) in das gleiche Zeichen mit vorausgehendem Gegenstrich
\
um, also aus*+
wird\*\+
. Alle anderen Zeichen bleiben gleich. for (var ii = 0; ii < string.length; ii++) {
In einer Schleife werden alle Zeichen des Parametersstring
bearbeitet.cc = string[ii]
Das aktuelle Zeichen wird in der Variablencc
bereitgestelltif ('()[]{}?*+-|^\$\\.&~# \t\n'.contains(cc))
Der String'()[]{}?*+-|^\$\\.&~# \t\n'
enthält alle Metazeichen, mittels der Methodecontains()
wird festgestellt, ob das Zeichencc
sich darin befindet.rc += r'\';
Wenn ja, wird ein Gegenstrich ans Ergebnis angeheftet.rc += cc;
Jetzt wird das Zeichen selbst angehängt.
Die Funktion shellPatternToRegExp()
Diese Funktion wandelt ein Suchmuster, wie sie in Kommandozeile üblich ist, in den String
eines analogen regulären Ausdrucks um: *.txt
wird zu .*\.txt
.
- Das Jokerzeichen
*
(beliebiger String) wird zu.*
. - Das Jokerzeichen
?
(genau ein beliebiges Zeichen) wird zu.
. - Eine negierte Zeichenklasse
[!X]
wird zu[^X]
. - In Zeichenklassen werden bestimmte Zeichen mit vorausgehenem Gegenstrich
\
maskiert, damit sie nicht als Metazeichen interpretiert werden.
/// Translates a unix shell pattern into a regular expression pattern. /// Example: '*.txt' is translated into r'.*\.txt' /// Note: the algorithm is a simplified version of the algorithm in the Python standard library. /// [addBeginOfString]: true: the result starts with '^'. /// [addEndOfString]: true: the result ends with r'$'. String shellPatternToRegExp(String pattern, {bool addBeginOfString = true, bool addEndOfString = true}) { var rc; if (pattern == null) { rc = null; } else { var i = 0; var length = pattern.length; rc = ''; while (i < length) { final c = pattern[i++]; if (c == '*') { rc += '.*'; } else if (c == '?') { rc += '.'; } else if (c == '[') { var j = i; if (j < length && pattern[j] == '!') { j++; } if (j < length && pattern[j] == ']') { j++; } while (j < length && pattern[j] != ']') { j++; } if (j >= length) { rc += r'\[' + pattern.substring(i); break; } var stuff = pattern.substring(i, j); if (stuff[0] == '!') { stuff = '^' + stuff.substring(1); } stuff = stuff.replaceAll(r'\', r'\\').replaceAll(']', r'\]'); rc += '[$stuff]'; i = j + 1; } else { rc += regExprEscape(c); } } if (addBeginOfString) { rc = '^' + rc; } if (addEndOfString) { rc += r'$'; } } return rc; }
- Der Algorithmus ist aus der Standardroutine von Python abgeschaut, eine reine Fleißaufgabe, nichts Aufregendes oder Neues.
Die Funktion testIntArguments()
Die Funktion prüft, ob für eine Liste von Programmoptionen jeweils eine Ganzzahl eingegeben wurde.
/// Tests whether the [options] are integers. If not the callback usage is called. /// Returns true if all arguments are integers. bool testIntArguments( ArgResults argResults, List<String> options, Function usage) { var rc = true; for (var opt in options) { if (argResults[opt] != null && int.tryParse(argResults[opt]) == null) { usage('$opt is not an integer: ${argResults[opt]}'); rc = false; break; } } return rc; }
bool testIntArguments(ArgResults argResults, List<String> options, Function usage)
- Das Funktionsergebnis ist bool, als Argument wird übergeben:
- Ein Objekt der Klasse
ArgResults
aus dem Paketargs
(siehe Inport). - Die Liste der Optionsnamen, die überprüft werden soll:
options
- Eine Callbackfunktion, die im Fehlerfall aufgerufen wird.
- Ein Objekt der Klasse
- Das Funktionsergebnis ist bool, als Argument wird übergeben:
- In einer Schleife werden alle Elemente der Optionsnamensliste untersucht:
- Ist der Optionswert keine Zahl, dann wird die Callbackfunktion mit der Fehlermeldung als Parameter aufgerufen und der Ergebniswert auf
false
gesetzt.
Die Funktion testRegExpArguments()
Analogon zur Funktion testIntArguments()
, nur dass hier geprüft wird, ob ein syntaktisch korrekter regulärer Ausdruck vorliegt.
/// Tests whether the [options] are integers. If not the callback usage is called. /// Returns true if all arguments are integers. bool testRegExpArguments( ArgResults argResults, List<String> options, Function usage) { var rc = true; for (var opt in options) { try { if (argResults[opt] != null) { RegExp(argResults[opt]); } } on FormatException catch (exc) { usage('$opt: error in regular expression "${argResults[opt]}": $exc'); rc = false; break; } } return rc; }
Die Funktion writeString()
Diese Funktion schreibt eine String oder eine Stringliste in eine Datei. Es wird geprüft, ob das Verzeichnis, in der die Datei steht, existiert. Wenn nicht, wird dieses Verzeichnis angelegt.
/// Writes a [string] or a [list] into a [file]. /// The path is void writeString(String filename, {String string, List<String> list}) { try { final base = dirname(filename); if (base.isNotEmpty) { Directory(base).createSync(recursive: true); } if (list != null) { string = list.join('\n'); } File(filename).writeAsStringSync(string); } on FileSystemException catch (exc) { print('+++ $exc'); } }
void writeString(String filename, {String string, List<String> list}) {
- Es werden benannte Parameter benutzt:
string
undlist
. try { ... } on FileSystemException catch (exc) {
Passiert bei einer Dateioperation ein Fehler (z.B. keine Berechtigung beim Erzeugen der Datei), wird dieser Fehler gemeldet:print('+++ $exc');
- Es werden benannte Parameter benutzt:
final base = dirname(filename);
Wir verwenden das Paketpath
. Dort ist die Funktiondirname()
definiert, die den Namen des Verzeichnisses einer Datei ermittelt, für alle Betriebssysteme.if (base.isNotEmpty)
Wenn ein Verzeichnis Teil des Dateinamens ist...Directory(base).createSync(recursive: true);
- ... wird ein Verzeichnis angelegt, wenn es noch nicht existiert:
Directory
ist eine Klasse aus "dart:io", die als Konstruktor den Namen des Verzeichnisses bekommtcreateSync()
legt das Verzeichis mit allen "Vaterverzeichnissen" an. Wenn es schon existiert, passiert nichts.
- Die benannten Parameter
string
undlist
sind alternativ. - Im Fall von
list
wird diese in einen String umgewandelt, mit der Methodejoin()
, die alle Elemente zusammenfügt, mit dem als Parameter übergebenen Trenner, in unserem Fall ein Zeilenwechsel\n
File(filename).writeAsStringSync(string);
- Die Klasse
File
aus "dart:io" wird mit dem Dateinamen im Konstruktor instantiiert (als Objekt erstellt) - und der String wird in diese Datei geschrieben und zwar mit dem Zeichensatz UTF-8 (einen anderen Zeichensatz kann man per Parameter wählen).
- Die Klasse
- Geht beim Dateischreiben was schief, beispielsweise ein Rechteproblem, wird eine Ausnahme vom Typ
FileSystemException
geworfen. Im diesem Fall wird eine Fehlermeldung ausgegeben.on FileSystemException catch (exc)
das Objekt der Ausnahme steht in der Variablenexc
zur Verfügung- Wie jede Klasse hat auch
FileSystemException
eine MethodetoString()
, die bei der Interpretation von+++ $exc
die Fehlermeldung einbaut.