Tips for preventing the occurrence of a final empty line in Deno's TextLineStream

I executed this snippet of code:

import { TextLineStream } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="7201061632425c4341445c42">[email protected]</a>/streams/mod.ts";
const cmd1 = new Deno.Command("echo", {args:["foo\n\nbar"], stdout: "piped"}).spawn();
for await (const chunk of cmd1.stdout.pipeThrough(new TextDecoderStream).pipeThrough(new TextLineStream)) {
    console.log("result: ", chunk);
}

Resulted in:

result:  foo
result:
result:  bar
result:

Desired output:

result:  foo
result:
result:  bar

Is there a way to determine the end of the stream and prevent an empty line at the end?
Appreciate any assistance provided.

Answer №1

Latest Update: The issue mentioned has been resolved through a fix included in the pull request available at denoland/deno_std#3103. This fix has been successfully merged and will be part of the upcoming release version of the std library, starting from 0.173.0.


The data type of cmd1.stdout referenced in your query is ReadableStream<Uint8Array>. Upon passing this through other transform stream classes, the outcome transforms into a ReadableStream<string>. In scenarios where you utilize a for await...of loop, each yielded value is essentially a line (string), hence lacking an inherent reference to determine the end of the stream within the loop for decision-making. (This remains valid even if one were to manually iterate over the stream's

AsyncIterableIterator<string>
.) By the time the loop concludes after the stream ends, it becomes unfeasible to retroactively undo handling of the final (empty) line.

Nevertheless, combatting this predicament involves storing each previous line outside the loop in a variable, subsequently utilizing this preceding line inside the loop instead of the current line. Post-loop completion, assess whether the final previous line is devoid of content — if so, ignore it as required.

Below is a self-contained example that delivers identical results across all platforms without necessitating permissions. It draws upon the information in your inquiry, contrasting the presented input with varied outputs demonstrating diverse endings for comparison purposes. Additionally, it offers line numbers, aiding referencing against the default behavior of TextLineStream. Noteworthy code resides within the RefinedTextLineStream class, seamlessly implementable in your own code for achieving the desired output:

const stream = cmd1.stdout
  .pipeThrough(new TextDecoderStream())
  .pipeThrough(new TextLineStream())
  .pipeThrough(new RefinedTextLineStream());

for await (const [line] of stream) {
  console.log("result: ", line);
}

Here's the test scenario showcasing reproducible results:

so-74905946.ts:

import { TextLineStream } from "https://deno.land/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="097a7d6d493927383e392739">[email protected]</a>/streams/text_line_stream.ts";

type LineWithNumber = [line: string, lineNumber: number];

/** Custom implementation tailored for Deno's std library TextLineStream */
class RefinedTextLineStream extends TransformStream<string, LineWithNumber> {
  #lineNumber = 0;
  #previous = "";

  constructor() {
    super({
      transform: (textLine, controller) => {
        if (this.#lineNumber > 0) {
          controller.enqueue([this.#previous, this.#lineNumber]);
        }
        this.#lineNumber += 1;
        this.#previous = textLine;
      },
      flush: (controller) => {
        if (this.#previous.length > 0) {
          controller.enqueue([this.#previous, this.#lineNumber]);
        }
      },
    });
  }
}

const exampleInputs: [name: string, input: string][] = [
  ["input from question", "foo\n\nbar\n"],
  ["input with extra final line feed", "foo\n\nbar\n\n"],
  ["input without final line feed", "foo\n\nbar"],
];

for (const [name, input] of exampleInputs) {
  console.log(`\n${name}: ${JSON.stringify(input)}`);

  const [textLineStream1, textLineStream2] = new File([input], "untitled")
    .stream()
    .pipeThrough(new TextDecoderStream())
    .pipeThrough(new TextLineStream())
    .tee();

  console.log("\ndefault:");
  let lineNumber = 0;
  for await (const line of textLineStream1) {
    lineNumber += 1;
    console.log(lineNumber, line);
  }

  console.log("\nskipping empty final line:");
  const stream = textLineStream2.pipeThrough(new RefinedTextLineStream());
  for await (const [line, lineNumber] of stream) console.log(lineNumber, line);
}

To execute in the terminal:

% deno --version
deno 1.29.1 (release, x86_64-apple-darwin)
v8 10.9.194.5
typescript 4.9.4

% deno run so-74905946.ts  

input from question: "foo\n\nbar\n"

default:
1 foo
2 
3 bar
4 

skipping empty final line:
1 foo
2 
3 bar

input with extra final line feed: "foo\n\nbar\n\n"

default:
1 foo
2 
3 bar
4 
5 

skipping empty final line:
1 foo
2 
3 bar
4 

input without final line feed: "foo\n\nbar"

default:
1 foo
2 
3 bar

skipping empty final line:
1 foo
2 
3 bar

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Submit form only if the field value is valid

I created a form with an input field that captures the user's mobile number and validates it to ensure it begins with '0'. The validation process is functioning correctly, but I am encountering a problem when submitting the form. Even if the ...

Steps for assigning an id to a newly created div within a parent div while in design mode

Imagine creating a div element with the attribute contenteditable="true", and then checking the console for what happens next: 1st. In the console, you only see a simple div tag. <div id="typbody" contenteditable="true" style="width:100%; height:200px ...

Mixing up letters using a shuffle function

Seeking assistance as a newcomer here. I have a shuffle function triggered by pressing the reset button with class="reset" on the img tag. How can I make it load shuffled from the start? So that when loading this page, the letters do not appear in alphabet ...

Exploring the capabilities of require() in nodeJS

I'm wondering about the inner workings of the require() function in a nodeJS application. What exactly does require() return? Let's say I want to utilize two third-party packages: lodash and request. After installing these packages, my code mig ...

Toggling with Jquery when an image is clicked

I'm trying to wrap my head around the functionality of jquery toggle. My goal is to toggle to the next anchor element with the class plr-anchor when an image with the class go_down is clicked. The information is being populated using maps. Javascript ...

The visibility feature in knockout.js appears to be malfunctioning

I am relatively new to using knockout.js and I'm attempting to control the visibility of a label on a slider item based on a specific condition. Even when the condition is false, the label still appears. Any suggestions would be greatly appreciated. ...

Updating tooltip text for checkbox dynamically in Angular 6

Can anyone help me with this code? I am trying to display different text in a tooltip based on whether a checkbox is active or not. For example, I want it to show "active" when the checkbox is active and "disactive" when it's inactive. Any suggestions ...

What causes the input field to lose focus in React after typing a character?

Currently utilizing React Mui for components and encountering no errors in either the Chrome inspector or terminal. How can this be resolved? No error notifications are being displayed by eslint or Chrome Inspector. The form submission functions correctl ...

Steps to access a Request object within a Controller

I am currently working with Express and Typescript, utilizing Controllers for managing requests. In an attempt to create a BaseController that includes the Request and Response objects for each request, I wrote the following code snippet. However, it see ...

Creating form inputs dynamically in HTML and then sending them to a PHP script programmatically

On my webpage, users can click a button to add new form inputs as needed. However, I'm running into an issue trying to access these new inputs in the PHP file where I submit the data. Here is the code snippet for the button within the form on the pag ...

Handsontable's unique text editor feature is encountering a tricky issue with copying and pasting

In my table, there are two columns: name and code. I have developed a simple custom editor for the code column, where the user can double click on a cell to open a custom dialog with a code editor. You can view a simplified example of this functionality he ...

Utilizing React, generate buttons on the fly that trigger the display of their respective

Looking for a way to dynamically display 3 buttons, each of which opens a different modal? I'm struggling to figure out how to properly link the buttons to their respective modals. Here's the code I've attempted so far: Button - Modal Code: ...

Dealing with unique constraint violation in Mongodb when using insertMany

Currently, I'm in the process of working on a project that involves using node.js and mongodb version 5. In my collection, I have implemented a unique index for the Parcel property. However, during testing, an error is triggered: MongoBulkWriteError: ...

I am facing difficulty importing emotion js style using dynamic variables

I recently designed a webpage that has the following appearance: https://i.stack.imgur.com/AnIXl.jpg Here is the code from my _app.tsx file: import '../styles/globals.css' import type { AppProps } from 'next/app' import { createTheme ...

Error: The method .map is not a valid function in this context

I've decided to build a small To-Do app in order to enhance my knowledge of ReactJS and React Hooks. However, I'm facing an issue with the list.map() function that I'm using. The error message keeps saying that it's not a function, but ...

Modify a single parameter of an element in a Map

Imagine I have a map data type exampleMap: Map<string, any> The key in the map is always a string, and the corresponding value is an object. This object might look like this: { name: 'sampleName', age: 30} Now, let's say the user se ...

Validating a string using regular expressions in JavaScript

Input needed: A string that specifies the range of ZIP codes the user intends to use. Examples: If the user wants to use all zip codes from 1000 to 1200: They should enter 1000:1200 If they want to use only ZIP codes 1000 and 1200: They should enter ...

Implementing Passport authentication for Steam, transitioning from Express to NestJS

I have embarked on the task of transitioning an express project to nestjs. How can I achieve the same functionality in Nestjs as shown in the working code snippet from Express below? (The code simply redirects to the Steam sign-in page) /* eslint-disable s ...

Is it possible to nest Route components in react-router version 4.x?

How can one properly implement nested routes in react-router version 4.x? Previous methods like the one below worked well, but upgrading to version 4.x now results in a warning... <Route path='/stuff' component={Stuff}> <Route path=&a ...

Implement a new method called "defer" to an array that will be resolved at a later time using Promise.all()

I need to manage a queue of DB calls that will be executed only once the connection is established. The DB object is created and stored as a member of the module upon connection. DB Module: var db = { localDb: null, connectLocal: (dbName) => { ...