From e1b043c61f888abadecad7790353ff38a633fb52 Mon Sep 17 00:00:00 2001 From: "Erik C. Thauvin" Date: Wed, 13 Oct 2021 21:36:40 -0700 Subject: [PATCH] Made cat() a fully standalone library function. --- README.md | 49 ++++- bin/dcat.dart | 30 ++- doc/api/dcat/CatResult-class.html | 278 +++++++++++++++++++++++++ doc/api/dcat/CatResult/CatResult.html | 137 ++++++++++++ doc/api/dcat/CatResult/addMessage.html | 155 ++++++++++++++ doc/api/dcat/CatResult/exitCode.html | 143 +++++++++++++ doc/api/dcat/CatResult/messages.html | 143 +++++++++++++ doc/api/dcat/cat.html | 61 ++++-- doc/api/dcat/dcat-library.html | 38 ++-- doc/api/dcat/exitFailure-constant.html | 3 +- doc/api/dcat/exitSuccess-constant.html | 3 +- doc/api/dcat/printError.html | 145 ------------- doc/api/index.html | 36 +++- doc/api/index.json | 2 +- lib/dcat.dart | 108 ++++++---- pubspec.lock | 6 +- pubspec.yaml | 2 +- test/dcat_test.dart | 114 +++++++--- test/test.7z | Bin 460 -> 465 bytes 19 files changed, 1182 insertions(+), 271 deletions(-) create mode 100644 doc/api/dcat/CatResult-class.html create mode 100644 doc/api/dcat/CatResult/CatResult.html create mode 100644 doc/api/dcat/CatResult/addMessage.html create mode 100644 doc/api/dcat/CatResult/exitCode.html create mode 100644 doc/api/dcat/CatResult/messages.html delete mode 100644 doc/api/dcat/printError.html diff --git a/README.md b/README.md index 3ed52dd..fa9a18c 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ [![GitHub CI](https://github.com/ethauvin/dcat/actions/workflows/dart.yml/badge.svg)](https://github.com/ethauvin/dcat/actions/workflows/dart.yml) [![codecov](https://codecov.io/gh/ethauvin/dcat/branch/master/graph/badge.svg?token=9PC4K4IZXJ)](https://codecov.io/gh/ethauvin/dcat) -# dcat: Concatenate File(s) to Standard Output +# dcat: Concatenate File(s) to Standard Output or File -A **cat** command-line implementation in [Dart](https://dart.dev/), inspired by the [Write command-line apps sample code](https://dart.dev/tutorials/server/cmdline). +A **cat** command-line and library implementation in [Dart](https://dart.dev/), inspired by the [Write command-line apps sample code](https://dart.dev/tutorials/server/cmdline). ## Synopsis -**dcat** copies each file, or standard input if none are given, to standard output. +**dcat** copies each file, or standard input if none are given, to standard output or file. ## Command-Line Usage @@ -49,6 +49,49 @@ dart compile exe -o bin/dcat bin/dcat.dart dart compile exe bin/dcat.dart ``` +## Library Usage +```dart +import 'package:dcat/dcat.dart'; + +final result = await cat(['path/to/file', 'path/to/otherfile]'], File('path/to/outfile')); +if (result.exitCode == exitFailure) { + for (final message in result.messages) { + print("Error: $message"); + } +} +``` + +The `cat` function supports the following parameters: + +Parameter | Description | Type +:--------------- |:----------------------------- | :------------------- +paths | The file paths. | String[] +output | The standard output or file. | IOSink or File +input | The standard input. | Stream\>? +log | The log for debugging. | List\? +showEnds | Same as `-e` | bool +numberNonBlank | Same as `-b` | bool +showLineNumbers | Same as `-n` | bool +showTabs | Same as `-T` | bool +squeezeBlank | Same as `-s` | bool +showNonPrinting | Same as `-v` | bool + +* `paths` and `output` are required. +* `output` should be an `IOSink` like `stdout` or a `File`. +* `input` can be `stdin`. +* `log` is used for debugging/testing purposes. + +The remaining optional parameters are similar to the [GNU cat](https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation) utility. + +A `CatResult` object is returned which contains the `exitCode` (`exitSuccess` or `exitFailure`) and error `messages` if any: + +```dart +final result = await cat(['path/to/file'], stdout); +if (result.exitCode == exitSuccess) { + ... +} +``` + ## Differences from [GNU cat](https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation) - No binary file support. - A line is considered terminated by either a `CR` (carriage return), a `LF` (line feed), a `CR+LF` sequence (DOS line ending). diff --git a/bin/dcat.dart b/bin/dcat.dart index 0603278..4626893 100644 --- a/bin/dcat.dart +++ b/bin/dcat.dart @@ -27,8 +27,9 @@ const versionFlag = 'version'; /// Usage: `dcat [option] [file]…` Future main(List arguments) async { final parser = ArgParser(); - Future returnCode; + exitCode = exitSuccess; + parser.addFlag(showAllFlag, negatable: false, abbr: 'A', help: 'equivalent to -vET'); parser.addFlag(nonBlankFlag, @@ -63,16 +64,14 @@ Future main(List arguments) async { try { argResults = parser.parse(arguments); } on FormatException catch (e) { - exitCode = await printError( - "${e.message}\nTry '$appName --$helpFlag' for more information.", - appName: appName); - return exitCode; + return printError( + "${e.message}\nTry '$appName --$helpFlag' for more information."); } if (argResults[helpFlag]) { - returnCode = usage(parser.usage); + exitCode = await usage(parser.usage); } else if (argResults[versionFlag]) { - returnCode = printVersion(); + exitCode = await printVersion(); } else { final paths = argResults.rest; var showEnds = argResults[showEndsFlag]; @@ -91,20 +90,31 @@ Future main(List arguments) async { showNonPrinting = showEnds = showTabs = true; } - returnCode = cat(paths, - appName: appName, + final result = await cat(paths, stdout, + input: stdin, showEnds: showEnds, showLineNumbers: argResults[numberFlag], numberNonBlank: argResults[nonBlankFlag], showTabs: showTabs, squeezeBlank: argResults[squeezeBlank], showNonPrinting: showNonPrinting); + + for (final message in result.messages) { + await printError(message); + } + + exitCode = result.exitCode; } - exitCode = await returnCode; return exitCode; } +/// Prints the error [message] to [stderr]. +Future printError(String message) async { + stderr.writeln("$appName: $message"); + return exitFailure; +} + /// Prints the version info. Future printVersion() async { print('''$appName (Dart cat) $appVersion diff --git a/doc/api/dcat/CatResult-class.html b/doc/api/dcat/CatResult-class.html new file mode 100644 index 0000000..0b9dff6 --- /dev/null +++ b/doc/api/dcat/CatResult-class.html @@ -0,0 +1,278 @@ + + + + + + + + CatResult class - dcat library - Dart API + + + + + + + + + + + + + + + + + +
+ +
+ + +
CatResult
+ +
+ +
+ + +
+
+

CatResult class + Null safety + +

+ + +
+

Holds the cat result exitCode and error messages.

+
+ + + +
+

Constructors

+ +
+
+ CatResult() +
+
+ +
+
+
+ +
+

Properties

+ +
+
+ exitCode + int + +
+
+ The exit code. +
read / write
+ +
+ +
+ hashCode + int + +
+
+ The hash code for this object. [...] +
read-only, inherited
+ +
+ +
+ messages + List<String> + +
+
+ The error messages. +
final
+ +
+ +
+ runtimeType + Type + +
+
+ A representation of the runtime type of the object. +
read-only, inherited
+ +
+ +
+
+ +
+

Methods

+
+
+ addMessage(int exitCode, String message, {String? path}) + → void + + + +
+
+ Add a message. + + +
+ +
+ noSuchMethod(Invocation invocation) + → dynamic + + + +
+
+ Invoked when a non-existent method or property is accessed. [...] +
inherited
+ +
+ +
+ toString() + String + + + +
+
+ A string representation of this object. [...] +
inherited
+ +
+ +
+
+ +
+

Operators

+
+
+ operator ==(Object other) + bool + + + +
+
+ The equality operator. [...] +
inherited
+ +
+ +
+
+ + + + +
+ + + + + +
+ +
+ + dcat + 1.0.0 + + + +
+ + + + + + + + + + + + diff --git a/doc/api/dcat/CatResult/CatResult.html b/doc/api/dcat/CatResult/CatResult.html new file mode 100644 index 0000000..00dc26c --- /dev/null +++ b/doc/api/dcat/CatResult/CatResult.html @@ -0,0 +1,137 @@ + + + + + + + + CatResult constructor - CatResult class - dcat library - Dart API + + + + + + + + + + + + + + + + + +
+ +
+ + +
CatResult
+ +
+ +
+ + +
+
+

CatResult constructor + Null safety +

+ +
+ CatResult() +
+ + + + + +
+

Implementation

+
CatResult();
+
+ + +
+ + + + + +
+ +
+ + dcat + 1.0.0 + + + +
+ + + + + + + + + + + + diff --git a/doc/api/dcat/CatResult/addMessage.html b/doc/api/dcat/CatResult/addMessage.html new file mode 100644 index 0000000..ceb40a2 --- /dev/null +++ b/doc/api/dcat/CatResult/addMessage.html @@ -0,0 +1,155 @@ + + + + + + + + addMessage method - CatResult class - dcat library - Dart API + + + + + + + + + + + + + + + + + +
+ +
+ + +
addMessage
+ +
+ +
+ + +
+
+

addMessage method + Null safety +

+ +
+ + +void +addMessage(
  1. int exitCode,
  2. +
  3. String message,
  4. +
  5. {String? path}
  6. +
) + + + +
+ +
+

Add a message.

+
+ + + +
+

Implementation

+
void addMessage(int exitCode, String message, {String? path}) {
+  this.exitCode = exitCode;
+  if (path != null && path.isNotEmpty) {
+    messages.add('$path: $message');
+  } else {
+    messages.add(message);
+  }
+}
+
+ + +
+ + + + + +
+ +
+ + dcat + 1.0.0 + + + +
+ + + + + + + + + + + + diff --git a/doc/api/dcat/CatResult/exitCode.html b/doc/api/dcat/CatResult/exitCode.html new file mode 100644 index 0000000..78902db --- /dev/null +++ b/doc/api/dcat/CatResult/exitCode.html @@ -0,0 +1,143 @@ + + + + + + + + exitCode property - CatResult class - dcat library - Dart API + + + + + + + + + + + + + + + + + +
+ +
+ + +
exitCode
+ +
+ +
+ + +
+
+

exitCode property + Null safety +

+ +
+ int + exitCode +
read / write
+ +
+ +
+

The exit code.

+
+ + +
+

Implementation

+
int exitCode = exitSuccess;
+
+
+
+ + +
+ + + + + +
+ +
+ + dcat + 1.0.0 + + + +
+ + + + + + + + + + + + diff --git a/doc/api/dcat/CatResult/messages.html b/doc/api/dcat/CatResult/messages.html new file mode 100644 index 0000000..0163b3a --- /dev/null +++ b/doc/api/dcat/CatResult/messages.html @@ -0,0 +1,143 @@ + + + + + + + + messages property - CatResult class - dcat library - Dart API + + + + + + + + + + + + + + + + + +
+ +
+ + +
messages
+ +
+ +
+ + +
+
+

messages property + Null safety +

+ +
+ List<String> + messages +
final
+ +
+ +
+

The error messages.

+
+ + +
+

Implementation

+
final List<String> messages = [];
+
+
+
+ + +
+ + + + + +
+ +
+ + dcat + 1.0.0 + + + +
+ + + + + + + + + + + + diff --git a/doc/api/dcat/cat.html b/doc/api/dcat/cat.html index 47be894..975ead7 100644 --- a/doc/api/dcat/cat.html +++ b/doc/api/dcat/cat.html @@ -52,9 +52,10 @@
-Future<int> +Future<CatResult> cat(
  1. List<String> paths,
  2. -
  3. {String appName = '',
  4. +
  5. Object output,
  6. +
  7. {Stream<List<int>>? input,
  8. List<String>? log,
  9. bool showEnds = false,
  10. bool numberNonBlank = false,
  11. @@ -67,17 +68,21 @@
-

Concatenates files in paths to stdout

-

The parameters are similar to the GNU cat utility. -Specify a log for debugging or testing purpose.

+

Concatenates files in paths to stdout or File.

+
    +
  • output should be an IOSink like stdout or a File.
  • +
  • input can be stdin.
  • +
  • log is used for debugging/testing purposes.
  • +
+

The remaining optional parameters are similar to the GNU cat utility.

Implementation

-
Future<int> cat(List<String> paths,
-    {String appName = '',
+  
Future<CatResult> cat(List<String> paths, Object output,
+    {Stream<List<int>>? input,
     List<String>? log,
     bool showEnds = false,
     bool numberNonBlank = false,
@@ -85,19 +90,34 @@ Specify a log for debugging or testing purpose.

bool showTabs = false, bool squeezeBlank = false, bool showNonPrinting = false}) async { + var result = CatResult(); var lineNumber = 1; - var returnCode = 0; log?.clear(); if (paths.isEmpty) { - final lines = await _readStdin(); - await _writeLines(lines, lineNumber, log, showEnds, showLineNumbers, - numberNonBlank, showTabs, squeezeBlank, showNonPrinting); + if (input != null) { + final lines = await _readStream(input); + try { + await _writeLines( + lines, + lineNumber, + output, + log, + showEnds, + showLineNumbers, + numberNonBlank, + showTabs, + squeezeBlank, + showNonPrinting); + } catch (e) { + result.addMessage(exitFailure, '$e'); + } + } } else { for (final path in paths) { try { final Stream<String> lines; - if (path == '-') { - lines = await _readStdin(); + if (path == '-' && input != null) { + lines = await _readStream(input); } else { lines = utf8.decoder .bind(File(path).openRead()) @@ -106,6 +126,7 @@ Specify a log for debugging or testing purpose.

lineNumber = await _writeLines( lines, lineNumber, + output, log, showEnds, showLineNumbers, @@ -121,17 +142,16 @@ Specify a log for debugging or testing purpose.

} else { message = e.message; } - returnCode = await printError(message, appName: appName, path: path); + result.addMessage(exitFailure, message, path: path); } on FormatException { - returnCode = await printError('Binary file not supported.', - appName: appName, path: path); + result.addMessage(exitFailure, 'Binary file not supported.', + path: path); } catch (e) { - returnCode = - await printError(e.toString(), appName: appName, path: path); + result.addMessage(exitFailure, '$e', path: path); } } } - return returnCode; + return result; }
@@ -154,6 +174,8 @@ Specify a log for debugging or testing purpose.

dcat library
    +
  1. Classes
  2. +
  3. CatResult
  4. @@ -164,7 +186,6 @@ Specify a log for debugging or testing purpose.

  5. Functions
  6. cat
  7. -
  8. printError
  9. diff --git a/doc/api/dcat/dcat-library.html b/doc/api/dcat/dcat-library.html index 68ffe31..5ab06b8 100644 --- a/doc/api/dcat/dcat-library.html +++ b/doc/api/dcat/dcat-library.html @@ -50,10 +50,24 @@
    -

    Library to concatenate file(s) to standard output,

    +

    A library to concatenate files to standard output or file.

    +
    +

    Classes

    + +
    +
    + CatResult + +
    +
    + Holds the cat result exitCode and error messages. +
    + +
    +
    @@ -100,27 +114,14 @@
    - cat(List<String> paths, {String appName = '', List<String>? log, bool showEnds = false, bool numberNonBlank = false, bool showLineNumbers = false, bool showTabs = false, bool squeezeBlank = false, bool showNonPrinting = false}) - Future<int> + cat(List<String> paths, Object output, {Stream<List<int>>? input, List<String>? log, bool showEnds = false, bool numberNonBlank = false, bool showLineNumbers = false, bool showTabs = false, bool squeezeBlank = false, bool showNonPrinting = false}) + Future<CatResult>
    - Concatenates files in paths to stdout [...] - - -
    - -
    - printError(String message, {String appName = '', String path = ''}) - Future<int> - - - -
    -
    - Prints the appName, path and error message to stderr. + Concatenates files in paths to stdout or File. [...]
    @@ -157,6 +158,8 @@
    diff --git a/doc/api/index.json b/doc/api/index.json index 2af36b4..8909c48 100644 --- a/doc/api/index.json +++ b/doc/api/index.json @@ -1 +1 @@ -[{"name":"dcat","qualifiedName":"dcat","href":"dcat/dcat-library.html","type":"library","overriddenDepth":0,"packageName":"dcat"},{"name":"cat","qualifiedName":"dcat.cat","href":"dcat/cat.html","type":"function","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"exitFailure","qualifiedName":"dcat.exitFailure","href":"dcat/exitFailure-constant.html","type":"top-level constant","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"exitSuccess","qualifiedName":"dcat.exitSuccess","href":"dcat/exitSuccess-constant.html","type":"top-level constant","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"printError","qualifiedName":"dcat.printError","href":"dcat/printError.html","type":"function","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}}] +[{"name":"dcat","qualifiedName":"dcat","href":"dcat/dcat-library.html","type":"library","overriddenDepth":0,"packageName":"dcat"},{"name":"CatResult","qualifiedName":"dcat.CatResult","href":"dcat/CatResult-class.html","type":"class","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"CatResult","qualifiedName":"dcat.CatResult.CatResult","href":"dcat/CatResult/CatResult.html","type":"constructor","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"CatResult","type":"class"}},{"name":"addMessage","qualifiedName":"dcat.CatResult.addMessage","href":"dcat/CatResult/addMessage.html","type":"method","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"CatResult","type":"class"}},{"name":"exitCode","qualifiedName":"dcat.CatResult.exitCode","href":"dcat/CatResult/exitCode.html","type":"property","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"CatResult","type":"class"}},{"name":"messages","qualifiedName":"dcat.CatResult.messages","href":"dcat/CatResult/messages.html","type":"property","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"CatResult","type":"class"}},{"name":"cat","qualifiedName":"dcat.cat","href":"dcat/cat.html","type":"function","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"exitFailure","qualifiedName":"dcat.exitFailure","href":"dcat/exitFailure-constant.html","type":"top-level constant","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}},{"name":"exitSuccess","qualifiedName":"dcat.exitSuccess","href":"dcat/exitSuccess-constant.html","type":"top-level constant","overriddenDepth":0,"packageName":"dcat","enclosedBy":{"name":"dcat","type":"library"}}] diff --git a/lib/dcat.dart b/lib/dcat.dart index c95f422..6ab16ce 100644 --- a/lib/dcat.dart +++ b/lib/dcat.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. -/// Library to concatenate file(s) to standard output, +/// A library to concatenate files to standard output or file. library dcat; import 'dart:convert'; @@ -11,12 +11,35 @@ import 'dart:io'; const exitFailure = 1; const exitSuccess = 0; -/// Concatenates files in [paths] to [stdout] +/// Holds the [cat] result [exitCode] and error [messages]. +class CatResult { + /// The exit code. + int exitCode = exitSuccess; + /// The error messages. + final List messages = []; + + CatResult(); + + /// Add a message. + void addMessage(int exitCode, String message, {String? path}) { + this.exitCode = exitCode; + if (path != null && path.isNotEmpty) { + messages.add('$path: $message'); + } else { + messages.add(message); + } + } +} + +/// Concatenates files in [paths] to [stdout] or [File]. /// -/// The parameters are similar to the [GNU cat utility](https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation). -/// Specify a [log] for debugging or testing purpose. -Future cat(List paths, - {String appName = '', +/// * [output] should be an [IOSink] like [stdout] or a [File]. +/// * [input] can be [stdin]. +/// * [log] is used for debugging/testing purposes. +/// +/// The remaining optional parameters are similar to the [GNU cat utility](https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation). +Future cat(List paths, Object output, + {Stream>? input, List? log, bool showEnds = false, bool numberNonBlank = false, @@ -24,19 +47,34 @@ Future cat(List paths, bool showTabs = false, bool squeezeBlank = false, bool showNonPrinting = false}) async { + var result = CatResult(); var lineNumber = 1; - var returnCode = 0; log?.clear(); if (paths.isEmpty) { - final lines = await _readStdin(); - await _writeLines(lines, lineNumber, log, showEnds, showLineNumbers, - numberNonBlank, showTabs, squeezeBlank, showNonPrinting); + if (input != null) { + final lines = await _readStream(input); + try { + await _writeLines( + lines, + lineNumber, + output, + log, + showEnds, + showLineNumbers, + numberNonBlank, + showTabs, + squeezeBlank, + showNonPrinting); + } catch (e) { + result.addMessage(exitFailure, '$e'); + } + } } else { for (final path in paths) { try { final Stream lines; - if (path == '-') { - lines = await _readStdin(); + if (path == '-' && input != null) { + lines = await _readStream(input); } else { lines = utf8.decoder .bind(File(path).openRead()) @@ -45,6 +83,7 @@ Future cat(List paths, lineNumber = await _writeLines( lines, lineNumber, + output, log, showEnds, showLineNumbers, @@ -60,17 +99,16 @@ Future cat(List paths, } else { message = e.message; } - returnCode = await printError(message, appName: appName, path: path); + result.addMessage(exitFailure, message, path: path); } on FormatException { - returnCode = await printError('Binary file not supported.', - appName: appName, path: path); + result.addMessage(exitFailure, 'Binary file not supported.', + path: path); } catch (e) { - returnCode = - await printError(e.toString(), appName: appName, path: path); + result.addMessage(exitFailure, '$e', path: path); } } } - return returnCode; + return result; } /// Parses line with non-printing characters. @@ -83,7 +121,7 @@ Future _parseNonPrinting(String line, bool showTabs) async { } else if (ch == 127) { sb.write('^?'); } else { - sb.write('U+' + ch.toRadixString(16).padLeft(4, '0').toUpperCase()); + sb.write('U+' + ch.toRadixString(16).padLeft(4, '0').toUpperCase()); } } else if (ch == 9 && !showTabs) { sb.write('\t'); @@ -96,25 +134,12 @@ Future _parseNonPrinting(String line, bool showTabs) async { return sb.toString(); } -/// Prints the [appName], [path] and error [message] to [stderr]. -Future printError(String message, - {String appName = '', String path = ''}) async { - if (appName.isNotEmpty) { - stderr.write('$appName: '); - } - if (path.isNotEmpty) { - stderr.write('$path: '); - } - stderr.writeln(message); - return exitFailure; -} - -/// Reads from stdin. -Future> _readStdin() async => - stdin.transform(utf8.decoder).transform(const LineSplitter()); +/// Reads from stream (stdin, etc.) +Future> _readStream(Stream> input) async => + input.transform(utf8.decoder).transform(const LineSplitter()); /// Writes lines to stdout. -Future _writeLines(Stream lines, int lineNumber, +Future _writeLines(Stream lines, int lineNumber, Object out, [List? log, bool showEnds = false, bool showLineNumbers = false, @@ -150,7 +175,16 @@ Future _writeLines(Stream lines, int lineNumber, } log?.add(sb.toString()); - stdout.writeln(sb); + + try { + if (out is IOSink) { + out.writeln(sb); + } else if (out is File) { + await out.writeAsString("$sb\n", mode: FileMode.append); + } + } catch (e) { + rethrow; + } } return lineNumber; } diff --git a/pubspec.lock b/pubspec.lock index 5156e06..84b7283 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "28.0.0" + version: "29.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.5.0" + version: "2.6.0" args: dependency: "direct main" description: @@ -49,7 +49,7 @@ packages: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.3.5" collection: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 80aa13d..fa4356c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: dcat -description: Concatenate file(s) to standard output. +description: Concatenate file(s) to standard output or file version: 1.0.0 homepage: https://github.com/ethauvin/dcat diff --git a/test/dcat_test.dart b/test/dcat_test.dart index 1fa3d30..205af4a 100644 --- a/test/dcat_test.dart +++ b/test/dcat_test.dart @@ -2,56 +2,72 @@ // Use of this source code is governed by a BSD-style license that can be found // in the LICENSE file. +import 'dart:io'; + import 'package:dcat/dcat.dart'; import 'package:test/test.dart'; import '../bin/dcat.dart' as app; void main() { - final List log = []; int exitCode; + final List log = []; + final sampleBinary = 'test/test.7z'; + final sampleFile = 'test/test.txt'; + final sampleText = 'This is a test'; + final sourceFile = 'bin/dcat.dart'; + final tempDir = Directory.systemTemp.createTempSync(); + + Stream> mockStdin() async* { + yield sampleText.codeUnits; + } + + File tmpFile() => + File("${tempDir.path}/tmp-${DateTime.now().millisecondsSinceEpoch}.txt"); + + tearDownAll(() => tempDir.delete(recursive: true)); group('app', () { test('Test Help', () async { - expect(app.main(['-h']), completion(equals(0))); - expect(app.main(['--help']), completion(equals(0))); + expect(app.main(['-h']), completion(0)); + expect(app.main(['--help']), completion(0)); exitCode = await app.main(['-h']); - expect(exitCode, equals(exitSuccess)); + expect(exitCode, exitSuccess); }); test('Test --version', () async { - expect(app.main(['--version']), completion(equals(0))); + expect(app.main(['--version']), completion(0)); exitCode = await app.main(['--version']); - expect(exitCode, equals(exitSuccess)); + expect(exitCode, exitSuccess); }); test('Test -a', () async { - expect(app.main(['-a']), completion(equals(1))); + expect(app.main(['-a']), completion(1)); exitCode = await app.main(['-a']); - expect(exitCode, equals(exitFailure)); + expect(exitCode, exitFailure); }); test('Test directory', () async { exitCode = await app.main(['bin']); - expect(exitCode, equals(exitFailure)); + expect(exitCode, exitFailure); }); test('Test binary', () async { - exitCode = await app.main(['test/test.7z']); - expect(exitCode, equals(exitFailure)); + exitCode = await app.main([sampleBinary]); + expect(exitCode, exitFailure); }); test('Test missing file', () async { exitCode = await app.main(['foo']); - expect(exitCode, equals(exitFailure), reason: 'foo not found'); - exitCode = await app.main(['bin/dcat.dart', 'foo']); - expect(exitCode, equals(exitFailure), reason: 'one missing file'); + expect(exitCode, exitFailure, reason: 'foo not found'); + exitCode = await app.main([sourceFile, 'foo']); + expect(exitCode, exitFailure, reason: 'one missing file'); }); }); group('lib', () { test('Test cat source', () async { - await cat(['bin/dcat.dart'], log: log); + await cat([sourceFile], stdout, log: log); expect(log.isEmpty, false, reason: 'log is empty'); expect(log.first, startsWith('// Copyright (c)'), reason: 'has copyright'); @@ -59,8 +75,9 @@ void main() { }); test('Test cat -n source', () async { - exitCode = await cat(['bin/dcat.dart'], log: log, showLineNumbers: true); - expect(exitCode, 0, reason: 'result code is 0'); + final result = + await cat([sourceFile], stdout, log: log, showLineNumbers: true); + expect(result.exitCode, 0, reason: 'result code is 0'); expect(log.first, startsWith(' 1 // Copyright (c)'), reason: 'has copyright'); expect(log.last, endsWith(' }'), reason: 'last line'); @@ -70,7 +87,7 @@ void main() { }); test('Test cat source test', () async { - await cat(['bin/dcat.dart', 'test/test.txt'], log: log); + await cat([sourceFile, sampleFile], stdout, log: log); expect(log.length, greaterThan(10), reason: 'more than 10 lines'); expect(log.first, startsWith('// Copyright'), reason: 'start with copyright'); @@ -78,7 +95,7 @@ void main() { }); test('Test cat -E', () async { - await cat(['test/test.txt'], log: log, showEnds: true); + await cat([sampleFile], stdout, log: log, showEnds: true); var hasBlank = false; for (final String line in log) { expect(line, endsWith('\$')); @@ -91,7 +108,7 @@ void main() { }); test('Test cat -bE', () async { - await cat(['test/test.txt'], + await cat([sampleFile], stdout, log: log, numberNonBlank: true, showEnds: true); var hasBlank = false; for (final String line in log) { @@ -104,7 +121,7 @@ void main() { }); test('Test cat -T', () async { - await cat(['test/test.txt'], log: log, showTabs: true); + await cat([sampleFile], stdout, log: log, showTabs: true); var hasTab = false; for (final String line in log) { if (line.startsWith('^I')) { @@ -116,7 +133,7 @@ void main() { }); test('Test cat -s', () async { - await cat(['test/test.txt'], log: log, squeezeBlank: true); + await cat([sampleFile], stdout, log: log, squeezeBlank: true); var hasSqueeze = true; var prevLine = 'foo'; for (final String line in log) { @@ -129,19 +146,19 @@ void main() { }); test('Test cat -A', () async { - await cat(['test/test.txt'], + await cat([sampleFile], stdout, log: log, showNonPrinting: true, showEnds: true, showTabs: true); expect(log.last, equals('^I^A^B^C^DU+00A9^?U+0080U+2713\$')); }); test('Test cat -t', () async { - await cat(['test/test.txt'], + await cat([sampleFile], stdout, log: log, showNonPrinting: true, showTabs: true); expect(log.last, equals('^I^A^B^C^DU+00A9^?U+0080U+2713')); }); test('Test cat-Abs', () async { - await cat(['test/test.txt'], + await cat([sampleFile], stdout, log: log, showNonPrinting: true, showEnds: true, @@ -158,7 +175,7 @@ void main() { }); test('Test cat -v', () async { - await cat(['test/test.txt'], log: log, showNonPrinting: true); + await cat([sampleFile], stdout, log: log, showNonPrinting: true); var hasTab = false; for (final String line in log) { if (line.contains('\t')) { @@ -170,5 +187,50 @@ void main() { expect(log.last, equals('\t^A^B^C^DU+00A9^?U+0080U+2713'), reason: 'non-printing'); }); + + test('Test cat to file', () async { + final tmp = tmpFile(); + final result = await cat([sampleFile], tmp, log: log); + expect(result.exitCode, exitSuccess, reason: 'result code is success'); + expect(result.messages.length, 0, reason: 'messages is empty'); + expect(await tmp.exists(), true, reason: 'tmp file exists'); + expect(await tmp.length(), greaterThan(0), + reason: 'tmp file is not empty'); + var lines = await tmp.readAsLines(); + expect(lines.first, startsWith('Lorem'), reason: 'Lorem in first line'); + expect(lines.last, endsWith('✓'), reason: 'end with checkmark'); + }); + + test('Test cat with file and binary', () async { + final tmp = tmpFile(); + final result = await cat([sampleFile, sampleBinary], tmp, log: log); + expect(result.exitCode, exitFailure, reason: 'result code is failure'); + expect(result.messages.length, 1, reason: 'as one message'); + expect(result.messages.first, contains('Binary'), + reason: 'message contains binary'); + }); + + test('Test empty stdin', () async { + final tmp = tmpFile(); + var result = await cat([], tmp, input: Stream.empty()); + expect(result.exitCode, exitSuccess, reason: 'cat() is successful'); + expect(result.messages.length, 0, reason: 'cat() has no message'); + + result = await cat(['-'], tmp, input: Stream.empty()); + expect(result.exitCode, exitSuccess, reason: 'cat(-) is successful'); + expect(result.messages.length, 0, reason: 'cat(-) no message'); + }); + + test('Test cat with stdin', () async { + var tmp = tmpFile(); + var result = await cat(['-'], tmp, input: mockStdin()); + expect(result.exitCode, exitSuccess, reason: 'result code is failure'); + expect(result.messages.length, 0, reason: 'no message'); + var lines = await tmp.readAsLines(); + tmp = tmpFile(); + expect(await tmp.exists(), false, reason: 'tmp file does not exists'); + result = await cat([], tmp, input: mockStdin()); + expect(lines.first, equals(sampleText), reason: 'cat() is sample text'); + }); }); } diff --git a/test/test.7z b/test/test.7z index 816a01a735f5c1a646d42e554c9e5d20f325420f..98a416af4b9f1a2a4565d09a93f92d71b784b2a5 100644 GIT binary patch delta 113 zcmX@Ze34nzylT&Sbr}YhrmrED;fxGm5Cx$aVr7bsJz%`Z=)Y0bg;Be$`SW8mOolzv&Yc=8lRZ6Su7@7Y@Wt}}|VF)}DLY-wa* F001OE90>pb