Added show non-printing option.

This commit is contained in:
Erik C. Thauvin 2021-10-10 20:23:09 -07:00
parent 4948aaa970
commit 9397ebaa37
14 changed files with 316 additions and 298 deletions

View file

@ -5,6 +5,10 @@
A **cat** command-line implemenation 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.
## Command-Line Usage
```sh
@ -16,19 +20,23 @@ Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
-b, --number-nonblank number nonempty output lines, overrides -n
-E, --show-ends display $ at end of each line
-h, --help display this help and exit
-n, --number number all output lines
-T, --show-tabs display TAB characters as ^I
-s, --squeeze-blank suppress repeated empty output lines
--version output version information and exit
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonempty output lines, overrides -n
-e, --show-nonprinting-ends equivalent to -vE
-E, --show-ends display $ at end of each line
-h, --help display this help and exit
-n, --number number all output lines
-t, --show-nonprinting-tabs equivalent to -vT
-T, --show-tabs display TAB characters as ^I
-s, --squeeze-blank suppress repeated empty output lines
--version output version information and exit
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
Examples:
dcat f - g Output f's contents, then standard input, then g's contents.
dcat Copy standard input to standard output.
```
## Compile Application
## Compile Standalone Application
### *nix
```sh
@ -42,4 +50,5 @@ dart compile exe bin/dcat.dart
## Differences from [GNU cat](https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation)
- No binary file support.
- Line numbers are printed as `X:` where `X` is the line number.
- A line is considered terminated by either a `CR` (carriage return), a `LF` (line feed), a `CR+LF` sequence (DOS line ending).
- The non-printing `M-^?` notation is always used for unicode characters.

View file

@ -8,33 +8,43 @@ import 'package:args/args.dart';
import 'package:dcat/dcat.dart';
import 'package:indent/indent.dart';
const appName = libName;
const appName = 'dcat';
const appVersion = '1.0.0';
const helpFlag = 'help';
const nonBlankFlag = 'number-nonblank';
const numberFlag = 'number';
const showAllFlag = 'show-all';
const showEndsFlag = 'show-ends';
const showNonPrintingEndsFlag = 'show-nonprinting-ends';
const showNonPrintingFlag = 'show-nonprinting';
const showNonPrintingTabsFlag = 'show-nonprinting-tabs';
const showTabsFlag = 'show-tabs';
const squeezeBlank = 'squeeze-blank';
const versionFlag = 'version';
/// Concatenates files specified in [arguments].
///
/// Usage: `dcat [OPTION]... [FILE]...`
/// Usage: `dcat [option] [file]`
Future<int> main(List<String> arguments) async {
final parser = ArgParser();
Future<int> returnCode;
exitCode = exitSuccess;
parser.addFlag(showAllFlag,
negatable: false, abbr: 'A', help: 'equivalent to -vET');
parser.addFlag(nonBlankFlag,
negatable: false,
abbr: 'b',
help: 'number nonempty output lines, overrides -n');
parser.addFlag(showNonPrintingEndsFlag,
negatable: false, abbr: 'e', help: 'equivalent to -vE');
parser.addFlag(showEndsFlag,
negatable: false, abbr: 'E', help: 'display \$ at end of each line');
parser.addFlag(helpFlag,
negatable: false, abbr: 'h', help: 'display this help and exit');
parser.addFlag(numberFlag,
negatable: false, abbr: 'n', help: 'number all output lines');
parser.addFlag(showNonPrintingTabsFlag,
negatable: false, abbr: 't', help: 'equivalent to -vT');
parser.addFlag(showTabsFlag,
negatable: false, abbr: 'T', help: 'display TAB characters as ^I');
parser.addFlag(squeezeBlank,
@ -43,6 +53,11 @@ Future<int> main(List<String> arguments) async {
help: 'suppress repeated empty output lines');
parser.addFlag(versionFlag,
negatable: false, help: 'output version information and exit');
parser.addFlag('ignored', negatable: false, hide: true, abbr: 'u');
parser.addFlag(showNonPrintingFlag,
negatable: false,
abbr: 'v',
help: 'use ^ and M- notation, except for LFD and TAB');
final ArgResults argResults;
try {
@ -59,12 +74,30 @@ Future<int> main(List<String> arguments) async {
returnCode = printVersion();
} else {
final paths = argResults.rest;
var showEnds = argResults[showEndsFlag];
var showTabs = argResults[showTabsFlag];
var showNonPrinting = argResults[showNonPrintingFlag];
if (argResults[showNonPrintingEndsFlag]) {
showNonPrinting = showEnds = true;
}
if (argResults[showNonPrintingTabsFlag]) {
showNonPrinting = showTabs = true;
}
if (argResults[showAllFlag]) {
showNonPrinting = showEnds = showTabs = true;
}
returnCode = cat(paths,
showEnds: argResults[showEndsFlag],
appName: appName,
showEnds: showEnds,
showLineNumbers: argResults[numberFlag],
numberNonBlank: argResults[nonBlankFlag],
showTabs: argResults[showTabsFlag],
squeezeBlank: argResults[squeezeBlank]);
showTabs: showTabs,
squeezeBlank: argResults[squeezeBlank],
showNonPrinting: showNonPrinting);
}
exitCode = await returnCode;
@ -94,6 +127,6 @@ Examples:
$appName f - g Output f's contents, then standard input, then g's contents.
$appName Copy standard input to standard output.
Source and documentation: <https://github.com/ethauvin/dcat>''');
Source and documentation: <https://github.com/ethauvin/dcat>''');
return exitSuccess;
}

View file

@ -54,12 +54,14 @@
<span class="returntype"><a href="https://api.dart.dev/stable/2.14.3/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/int-class.html">int</a></span>&gt;</span></span>
<span class="name ">cat</span>(<wbr><ol class="parameter-list"><li><span class="parameter" id="cat-param-paths"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span></span> <span class="parameter-name">paths</span>, </span></li>
<li><span class="parameter" id="cat-param-log">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span>?</span> <span class="parameter-name">log</span>, </span></li>
<li><span class="parameter" id="cat-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">&#39;&#39;</span>, </span></li>
<li><span class="parameter" id="cat-param-log"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span>?</span> <span class="parameter-name">log</span>, </span></li>
<li><span class="parameter" id="cat-param-showEnds"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showEnds</span> = <span class="default-value">false</span>, </span></li>
<li><span class="parameter" id="cat-param-numberNonBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">numberNonBlank</span> = <span class="default-value">false</span>, </span></li>
<li><span class="parameter" id="cat-param-showLineNumbers"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showLineNumbers</span> = <span class="default-value">false</span>, </span></li>
<li><span class="parameter" id="cat-param-showTabs"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showTabs</span> = <span class="default-value">false</span>, </span></li>
<li><span class="parameter" id="cat-param-squeezeBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">squeezeBlank</span> = <span class="default-value">false</span>}</span></li>
<li><span class="parameter" id="cat-param-squeezeBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">squeezeBlank</span> = <span class="default-value">false</span>, </span></li>
<li><span class="parameter" id="cat-param-showNonPrinting"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showNonPrinting</span> = <span class="default-value">false</span>}</span></li>
</ol>)
</section>
@ -67,7 +69,7 @@
<section class="desc markdown">
<p>Concatenates files in <code>paths</code> to <a href="https://api.dart.dev/stable/2.14.3/dart-io/stdout.html">stdout</a></p>
<p>The parameters are similar to the <a href="https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation">GNU cat utility</a>.
Specify a <code>log</code> for debugging purpose.</p>
Specify a <code>log</code> for debugging or testing purpose.</p>
</section>
@ -75,19 +77,21 @@ Specify a <code>log</code> for debugging purpose.</p>
<section class="summary source-code" id="source">
<h2><span>Implementation</span></h2>
<pre class="language-dart"><code class="language-dart">Future&lt;int&gt; cat(List&lt;String&gt; paths,
{List&lt;String&gt;? log,
{String appName = &#39;&#39;,
List&lt;String&gt;? log,
bool showEnds = false,
bool numberNonBlank = false,
bool showLineNumbers = false,
bool showTabs = false,
bool squeezeBlank = false}) async {
bool squeezeBlank = false,
bool showNonPrinting = false}) async {
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);
numberNonBlank, showTabs, squeezeBlank, showNonPrinting);
} else {
for (final path in paths) {
try {
@ -99,8 +103,16 @@ Specify a <code>log</code> for debugging purpose.</p>
.bind(File(path).openRead())
.transform(const LineSplitter());
}
lineNumber = await _writeLines(lines, lineNumber, log, showEnds,
showLineNumbers, numberNonBlank, showTabs, squeezeBlank);
lineNumber = await _writeLines(
lines,
lineNumber,
log,
showEnds,
showLineNumbers,
numberNonBlank,
showTabs,
squeezeBlank,
showNonPrinting);
} on FileSystemException catch (e) {
final String? osMessage = e.osError?.message;
final String message;
@ -109,11 +121,13 @@ Specify a <code>log</code> for debugging purpose.</p>
} else {
message = e.message;
}
returnCode = await printError(message, path: path);
returnCode = await printError(message, appName: appName, path: path);
} on FormatException {
returnCode = await printError(&#39;Binary file not supported.&#39;, path: path);
returnCode = await printError(&#39;Binary file not supported.&#39;,
appName: appName, path: path);
} catch (e) {
returnCode = await printError(e.toString(), path: path);
returnCode =
await printError(e.toString(), appName: appName, path: path);
}
}
}
@ -146,7 +160,6 @@ Specify a <code>log</code> for debugging purpose.</p>
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>

View file

@ -49,6 +49,9 @@
</h1></div>
<section class="desc markdown">
<p>Library to concatenate file(s) to standard output,</p>
</section>
@ -88,21 +91,6 @@
</div>
</dd>
<dt id="libName" class="constant">
<span class="name "><a href="../dcat/libName-constant.html">libName</a></span>
<span class="signature">&#8594; const <a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>
</dt>
<dd>
<div>
<span class="signature"><code>&#39;dcat&#39;</code></span>
</div>
</dd>
</dl>
</section>
@ -112,7 +100,7 @@
<dl class="callables">
<dt id="cat" class="callable">
<span class="name"><a href="../dcat/cat.html">cat</a></span><span class="signature">(<wbr><span class="parameter" id="cat-param-paths"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span></span> <span class="parameter-name">paths</span>, </span><span class="parameter" id="cat-param-log">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span>?</span> <span class="parameter-name">log</span>, </span><span class="parameter" id="cat-param-showEnds"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showEnds</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-numberNonBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">numberNonBlank</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-showLineNumbers"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showLineNumbers</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-showTabs"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showTabs</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-squeezeBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">squeezeBlank</span> = <span class="default-value">false</span>}</span>)
<span class="name"><a href="../dcat/cat.html">cat</a></span><span class="signature">(<wbr><span class="parameter" id="cat-param-paths"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span></span> <span class="parameter-name">paths</span>, </span><span class="parameter" id="cat-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">&#39;&#39;</span>, </span><span class="parameter" id="cat-param-log"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/List-class.html">List</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span>&gt;</span>?</span> <span class="parameter-name">log</span>, </span><span class="parameter" id="cat-param-showEnds"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showEnds</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-numberNonBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">numberNonBlank</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-showLineNumbers"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showLineNumbers</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-showTabs"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showTabs</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-squeezeBlank"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">squeezeBlank</span> = <span class="default-value">false</span>, </span><span class="parameter" id="cat-param-showNonPrinting"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/bool-class.html">bool</a></span> <span class="parameter-name">showNonPrinting</span> = <span class="default-value">false</span>}</span>)
<span class="returntype parameter">&#8594; <a href="https://api.dart.dev/stable/2.14.3/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/int-class.html">int</a></span>&gt;</span></span>
</span>
@ -125,7 +113,7 @@
</dd>
<dt id="printError" class="callable">
<span class="name"><a href="../dcat/printError.html">printError</a></span><span class="signature">(<wbr><span class="parameter" id="printError-param-message"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">message</span>, </span><span class="parameter" id="printError-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">libName</span>, </span><span class="parameter" id="printError-param-path"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">path</span> = <span class="default-value">&#39;&#39;</span>}</span>)
<span class="name"><a href="../dcat/printError.html">printError</a></span><span class="signature">(<wbr><span class="parameter" id="printError-param-message"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">message</span>, </span><span class="parameter" id="printError-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">&#39;&#39;</span>, </span><span class="parameter" id="printError-param-path"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">path</span> = <span class="default-value">&#39;&#39;</span>}</span>)
<span class="returntype parameter">&#8594; <a href="https://api.dart.dev/stable/2.14.3/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/int-class.html">int</a></span>&gt;</span></span>
</span>
@ -175,7 +163,6 @@
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>

View file

@ -89,7 +89,6 @@
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>

View file

@ -89,7 +89,6 @@
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>

View file

@ -1,129 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1, user-scalable=no">
<meta name="description" content="API docs for the libName constant from the dcat library, for the Dart programming language.">
<title>libName constant - dcat library - Dart API</title>
<link rel="preconnect" href="https://fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,300;0,400;0,500;0,700;1,400&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="../static-assets/github.css?v1">
<link rel="stylesheet" href="../static-assets/styles.css?v1">
<link rel="icon" href="../static-assets/favicon.png?v1">
</head>
<body data-base-href="../"
data-using-base-href="false">
<div id="overlay-under-drawer"></div>
<header id="title">
<button id="sidenav-left-toggle" type="button">&nbsp;</button>
<ol class="breadcrumbs gt-separated dark hidden-xs">
<li><a href="../index.html">dcat</a></li>
<li><a href="../dcat/dcat-library.html">dcat</a></li>
<li class="self-crumb">libName constant</li>
</ol>
<div class="self-name">libName</div>
<form class="search navbar-right" role="search">
<input type="text" id="search-box" autocomplete="off" disabled class="form-control typeahead" placeholder="Loading search...">
</form>
</header>
<main>
<div id="dartdoc-main-content" class="main-content">
<div>
<h1><span class="kind-top-level-property">libName</span> top-level constant
<a href="https://dart.dev/null-safety" class="feature feature-null-safety" title="Supports the null safety language feature.">Null safety</a>
</h1></div>
<section class="multi-line-signature">
<a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a>
const <span class="name ">libName</span>
</section>
<section class="summary source-code" id="source">
<h2><span>Implementation</span></h2>
<pre class="language-dart"><code class="language-dart">const libName = &#39;dcat&#39;;</code></pre>
</section>
</div> <!-- /.main-content -->
<div id="dartdoc-sidebar-left" class="sidebar sidebar-offcanvas-left">
<header id="header-search-sidebar" class="hidden-l">
<form class="search-sidebar" role="search">
<input type="text" id="search-sidebar" autocomplete="off" disabled class="form-control typeahead" placeholder="Loading search...">
</form>
</header>
<ol class="breadcrumbs gt-separated dark hidden-l" id="sidebar-nav">
<li><a href="../index.html">dcat</a></li>
<li><a href="../dcat/dcat-library.html">dcat</a></li>
<li class="self-crumb">libName constant</li>
</ol>
<h5>dcat library</h5>
<ol>
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>
<li><a href="../dcat/cat.html">cat</a></li>
<li><a href="../dcat/printError.html">printError</a></li>
</ol>
</div><!--/.sidebar-offcanvas-left-->
<div id="dartdoc-sidebar-right" class="sidebar sidebar-offcanvas-right">
</div><!--/.sidebar-offcanvas-->
</main>
<footer>
<span class="no-break">
dcat
1.0.0
</span>
</footer>
<script src="../static-assets/highlight.pack.js?v1"></script>
<script src="../static-assets/script.js?v1"></script>
</body>
</html>

View file

@ -54,7 +54,7 @@
<span class="returntype"><a href="https://api.dart.dev/stable/2.14.3/dart-async/Future-class.html">Future</a><span class="signature">&lt;<wbr><span class="type-parameter"><a href="https://api.dart.dev/stable/2.14.3/dart-core/int-class.html">int</a></span>&gt;</span></span>
<span class="name ">printError</span>(<wbr><ol class="parameter-list"><li><span class="parameter" id="printError-param-message"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">message</span>, </span></li>
<li><span class="parameter" id="printError-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">libName</span>, </span></li>
<li><span class="parameter" id="printError-param-appName">{<span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">appName</span> = <span class="default-value">&#39;&#39;</span>, </span></li>
<li><span class="parameter" id="printError-param-path"><span class="type-annotation"><a href="https://api.dart.dev/stable/2.14.3/dart-core/String-class.html">String</a></span> <span class="parameter-name">path</span> = <span class="default-value">&#39;&#39;</span>}</span></li>
</ol>)
@ -69,12 +69,14 @@
<section class="summary source-code" id="source">
<h2><span>Implementation</span></h2>
<pre class="language-dart"><code class="language-dart">Future&lt;int&gt; printError(String message,
{String appName = libName, String path = &#39;&#39;}) async {
if (path.isNotEmpty) {
stderr.writeln(&#39;$libName: $path: $message&#39;);
} else {
stderr.write(&#39;$libName: $message&#39;);
{String appName = &#39;&#39;, String path = &#39;&#39;}) async {
if (appName.isNotEmpty) {
stderr.write(&#39;$appName: &#39;);
}
if (path.isNotEmpty) {
stderr.write(&#39;$path: &#39;);
}
stderr.writeln(message);
return exitFailure;
}</code></pre>
</section>
@ -104,7 +106,6 @@
<li class="section-title"><a href="../dcat/dcat-library.html#constants">Constants</a></li>
<li><a href="../dcat/exitFailure-constant.html">exitFailure</a></li>
<li><a href="../dcat/exitSuccess-constant.html">exitSuccess</a></li>
<li><a href="../dcat/libName-constant.html">libName</a></li>
<li class="section-title"><a href="../dcat/dcat-library.html#functions">Functions</a></li>

View file

@ -48,6 +48,8 @@
<a href="https://github.com/ethauvin/dcat/actions/workflows/dart.yml"><img src="https://github.com/ethauvin/dcat/actions/workflows/dart.yml/badge.svg" alt="GitHub CI"></a></p>
<h1 id="dcat-concatenate-files-to-standard-output">dcat: Concatenate File(s) to Standard Output</h1>
<p>A <strong>cat</strong> command-line implemenation in <a href="https://dart.dev/">Dart</a>, inspired by the <a href="https://dart.dev/tutorials/server/cmdline">Write command-line apps sample code</a>.</p>
<h2 id="synopsis">Synopsis</h2>
<p><strong>dcat</strong> copies each file, or standard input if none are given, to standard output.</p>
<h2 id="command-line-usage">Command-Line Usage</h2>
<pre class="language-sh"><code class="language-sh">dcat --help
</code></pre>
@ -56,19 +58,23 @@ Concatenate FILE(s) to standard output.
With no FILE, or when FILE is -, read standard input.
-b, --number-nonblank number nonempty output lines, overrides -n
-E, --show-ends display $ at end of each line
-h, --help display this help and exit
-n, --number number all output lines
-T, --show-tabs display TAB characters as ^I
-s, --squeeze-blank suppress repeated empty output lines
--version output version information and exit
-A, --show-all equivalent to -vET
-b, --number-nonblank number nonempty output lines, overrides -n
-e, --show-nonprinting-ends equivalent to -vE
-E, --show-ends display $ at end of each line
-h, --help display this help and exit
-n, --number number all output lines
-t, --show-nonprinting-tabs equivalent to -vT
-T, --show-tabs display TAB characters as ^I
-s, --squeeze-blank suppress repeated empty output lines
--version output version information and exit
-v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
Examples:
dcat f - g Output f's contents, then standard input, then g's contents.
dcat Copy standard input to standard output.
</code></pre>
<h2 id="compile-application">Compile Application</h2>
<h2 id="compile-standalone-application">Compile Standalone Application</h2>
<h3 id="nix">*nix</h3>
<pre class="language-sh"><code class="language-sh">dart compile exe -o bin/dcat bin/dcat.dart
</code></pre>
@ -78,7 +84,8 @@ Examples:
<h2 id="differences-from-gnu-cathttpswwwgnuorgsoftwarecoreutilsmanualhtml_nodecat-invocationhtmlcat-invocation">Differences from <a href="https://www.gnu.org/software/coreutils/manual/html_node/cat-invocation.html#cat-invocation">GNU cat</a></h2>
<ul>
<li>No binary file support.</li>
<li>Line numbers are printed as <code>X:</code> where <code>X</code> is the line number.</li>
<li>A line is considered terminated by either a <code>CR</code> (carriage return), a <code>LF</code> (line feed), a <code>CR+LF</code> sequence (DOS line ending).</li>
<li>The non-printing <code>M-^?</code> notation is always used for unicode characters.</li>
</ul>
</section>
@ -90,7 +97,7 @@ Examples:
<span class="name"><a href="dcat/dcat-library.html">dcat</a></span>
</dt>
<dd>
<dd>Library to concatenate file(s) to standard output,
</dd>
</dl>

View file

@ -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":"libName","qualifiedName":"dcat.libName","href":"dcat/libName-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":"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"}}]

View file

@ -2,33 +2,35 @@
// 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,
library dcat;
import 'dart:convert';
import 'dart:io';
const libName = 'dcat';
const exitFailure = 1;
const exitSuccess = 0;
/// Concatenates files in [paths] to [stdout]
///
/// 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 purpose.
/// Specify a [log] for debugging or testing purpose.
Future<int> cat(List<String> paths,
{List<String>? log,
{String appName = '',
List<String>? log,
bool showEnds = false,
bool numberNonBlank = false,
bool showLineNumbers = false,
bool showTabs = false,
bool squeezeBlank = false}) async {
bool squeezeBlank = false,
bool showNonPrinting = false}) async {
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);
numberNonBlank, showTabs, squeezeBlank, showNonPrinting);
} else {
for (final path in paths) {
try {
@ -40,8 +42,16 @@ Future<int> cat(List<String> paths,
.bind(File(path).openRead())
.transform(const LineSplitter());
}
lineNumber = await _writeLines(lines, lineNumber, log, showEnds,
showLineNumbers, numberNonBlank, showTabs, squeezeBlank);
lineNumber = await _writeLines(
lines,
lineNumber,
log,
showEnds,
showLineNumbers,
numberNonBlank,
showTabs,
squeezeBlank,
showNonPrinting);
} on FileSystemException catch (e) {
final String? osMessage = e.osError?.message;
final String message;
@ -50,25 +60,63 @@ Future<int> cat(List<String> paths,
} else {
message = e.message;
}
returnCode = await printError(message, path: path);
returnCode = await printError(message, appName: appName, path: path);
} on FormatException {
returnCode = await printError('Binary file not supported.', path: path);
returnCode = await printError('Binary file not supported.',
appName: appName, path: path);
} catch (e) {
returnCode = await printError(e.toString(), path: path);
returnCode =
await printError(e.toString(), appName: appName, path: path);
}
}
}
return returnCode;
}
/// Parses line with non-printing characters.
Future<String> _parseNonPrinting(String line, bool showTabs) async {
final sb = StringBuffer();
for (var ch in line.runes) {
if (ch >= 32) {
if (ch < 127) {
sb.writeCharCode(ch);
} else if (ch == 127) {
sb.write('^?');
} else {
sb.write('M-');
if (ch >= 128 + 32) {
if (ch < 128 + 127) {
sb.writeCharCode(ch - 128);
} else {
sb.write('^?');
}
} else {
sb
..write('^')
..writeCharCode(ch - 128 + 64);
}
}
} else if (ch == 9 && !showTabs) {
sb.write('\t');
} else {
sb
..write('^')
..writeCharCode(ch + 64);
}
}
return sb.toString();
}
/// Prints the [appName], [path] and error [message] to [stderr].
Future<int> printError(String message,
{String appName = libName, String path = ''}) async {
if (path.isNotEmpty) {
stderr.writeln('$libName: $path: $message');
} else {
stderr.write('$libName: $message');
{String appName = '', String path = ''}) async {
if (appName.isNotEmpty) {
stderr.write('$appName: ');
}
if (path.isNotEmpty) {
stderr.write('$path: ');
}
stderr.writeln(message);
return exitFailure;
}
@ -83,7 +131,8 @@ Future<int> _writeLines(Stream<String> lines, int lineNumber,
bool showLineNumbers = false,
bool showNonBlank = false,
bool showTabs = false,
bool squeezeBlank = false]) async {
bool squeezeBlank = false,
bool showNonPrinting = false]) async {
var emptyLine = 0;
final sb = StringBuffer();
await for (final line in lines) {
@ -95,14 +144,18 @@ Future<int> _writeLines(Stream<String> lines, int lineNumber,
} else {
emptyLine = 0;
}
if (showNonBlank || showLineNumbers) {
sb.write('${lineNumber++}: ');
if (showLineNumbers || (showNonBlank && line.isNotEmpty)) {
sb.write('${lineNumber++} '.padLeft(8));
}
if (showTabs) {
if (showNonPrinting) {
sb.write(await _parseNonPrinting(line, showTabs));
} else if (showTabs) {
sb.write(line.replaceAll('\t', '^I'));
} else {
sb.write(line);
}
if (showEnds) {
sb.write('\$');
}

View file

@ -7,96 +7,139 @@ void main() {
final List<String> log = [];
int exitCode;
test('Test Help', () async {
expect(app.main(['-h']), completion(equals(0)));
expect(app.main(['--help']), completion(equals(0)));
exitCode = await app.main(['-h']);
expect(exitCode, equals(exitSuccess));
group('app', () {
test('Test Help', () async {
expect(app.main(['-h']), completion(equals(0)));
expect(app.main(['--help']), completion(equals(0)));
exitCode = await app.main(['-h']);
expect(exitCode, equals(exitSuccess));
});
test('Test --version', () async {
expect(app.main(['--version']), completion(equals(0)));
exitCode = await app.main(['--version']);
expect(exitCode, equals(exitSuccess));
});
test('Test directory', () async {
exitCode = await app.main(['bin']);
expect(exitCode, equals(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');
});
});
test('Test --version', () async {
expect(app.main(['--version']), completion(equals(0)));
exitCode = await app.main(['--version']);
expect(exitCode, equals(exitSuccess));
});
group('lib', () {
test('Test cat source', () async {
await cat(['bin/dcat.dart'], log: log);
expect(log.isEmpty, false, reason: 'log is empty');
expect(log.first, startsWith('// Copyright (c)'),
reason: 'has copyright');
expect(log.last, equals('}'));
});
test('Test directory', () async {
exitCode = await app.main(['bin']);
expect(exitCode, equals(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');
});
test('Test cat source', () async {
await cat(['bin/dcat.dart'], log: log);
expect(log.isEmpty, false, reason: 'log is empty');
expect(log.first, startsWith('// Copyright (c)'), reason: 'has copyright');
expect(log.last, equals('}'));
});
test('Test cat -n source', () async {
exitCode = await cat(['bin/dcat.dart'], log: log, showLineNumbers: true);
expect(exitCode, 0, reason: 'result code is 0');
expect(log.first, startsWith('1: // Copyright (c)'),
reason: 'has copyright');
expect(log.last, endsWith(': }'), reason: 'last line');
for (final String line in log) {
expect(line, matches('^\\d+: .*'), reason: 'has line number');
}
});
test('Test cat -E', () async {
await cat(['test/test.txt'], log: log, showEnds: true);
var hasBlank = false;
for (final String line in log) {
expect(line, endsWith('\$'));
if (line == '\$') {
hasBlank = true;
test('Test cat -n source', () async {
exitCode = await cat(['bin/dcat.dart'], log: log, showLineNumbers: true);
expect(exitCode, 0, reason: 'result code is 0');
expect(log.first, startsWith(' 1 // Copyright (c)'),
reason: 'has copyright');
expect(log.last, endsWith(' }'), reason: 'last line');
for (final String line in log) {
expect(line, matches('^ +\\d+ .*'), reason: 'has line number');
}
}
expect(hasBlank, true, reason: 'has blank line');
});
});
test('Test cat -bE', () async {
await cat(['test/test.txt'],
log: log, numberNonBlank: true, showEnds: true);
var hasBlank = false;
for (final String line in log) {
expect(line, endsWith('\$'));
if (line.contains(RegExp(r'^\d+: .*\$$'))) {
hasBlank = true;
}
}
expect(hasBlank, true, reason: 'has blank line');
});
test('Test cat source test', () async {
await cat(['bin/dcat.dart', 'test/test.txt'], log: log);
expect(log.length, greaterThan(10), reason: 'more than 10 lines');
expect(log.first, startsWith('// Copyright'),
reason: 'start with copyright');
expect(log.last, endsWith(''), reason: 'end with checkmark');
});
test('Test cat -T', () async {
await cat(['test/test.txt'], log: log, showTabs: true);
var hasTab = false;
for (final String line in log) {
if (line.startsWith('^I')) {
hasTab = true;
break;
test('Test cat -E', () async {
await cat(['test/test.txt'], log: log, showEnds: true);
var hasBlank = false;
for (final String line in log) {
expect(line, endsWith('\$'));
if (line == '\$') {
hasBlank = true;
}
}
}
expect(hasTab, true, reason: 'has tab');
});
expect(hasBlank, true, reason: 'has blank line');
expect(log.last, endsWith('\$'), reason: 'has unicode');
});
test('Test cat -s', () async {
await cat(['test/test.txt'], log: log, squeezeBlank: true);
var hasSqueeze = true;
var prevLine = 'foo';
for (final String line in log) {
if (line == prevLine) {
hasSqueeze = false;
test('Test cat -bE', () async {
await cat(['test/test.txt'],
log: log, numberNonBlank: true, showEnds: true);
var hasBlank = false;
for (final String line in log) {
expect(line, endsWith('\$'));
if (line.contains(RegExp(r'^ +\d+ .*\$$'))) {
hasBlank = true;
}
}
prevLine = line;
}
expect(hasSqueeze, true, reason: 'has squeeze');
expect(hasBlank, true, reason: 'has blank line');
});
test('Test cat -T', () async {
await cat(['test/test.txt'], log: log, showTabs: true);
var hasTab = false;
for (final String line in log) {
if (line.startsWith('^I')) {
hasTab = true;
break;
}
}
expect(hasTab, true, reason: 'has tab');
});
test('Test cat -s', () async {
await cat(['test/test.txt'], log: log, squeezeBlank: true);
var hasSqueeze = true;
var prevLine = 'foo';
for (final String line in log) {
if (line == prevLine) {
hasSqueeze = false;
}
prevLine = line;
}
expect(hasSqueeze, true, reason: 'has squeeze');
});
test('Test cat -A', () async {
await cat(['test/test.txt'],
log: log, showNonPrinting: true, showEnds: true, showTabs: true);
expect(log.last, equals('^I^A^B^C^DM-^?\$'));
});
test('Test cat -t', () async {
await cat(['test/test.txt'],
log: log, showNonPrinting: true, showTabs: true);
expect(log.last, equals('^I^A^B^C^DM-^?'));
});
test('Test cat-Abs', () async {
await cat(['test/test.txt'],
log: log,
showNonPrinting: true,
showEnds: true,
showTabs: true,
numberNonBlank: true,
squeezeBlank: true);
var blankLines = 0;
for (final String line in log) {
if (line == '\$') {
blankLines++;
}
}
expect(blankLines, 2, reason: 'only 2 blank lines.');
});
});
}

View file

@ -4,4 +4,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor i
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.