简体   繁体   中英

How to cleanly take multiline input from the terminal using Elixir?

As practice, I am trying to write a small Elixir script that will take in user input from the terminal and write it to a file. My goal is to be able to accept multiple lines of user input from the terminal at once (followed by some terminator in this current iteration).

My current and relatively naive approach does work as far as this initial mission is concerned: I have a recursive get_line/2 function that will take a line of input, concatenate it to all input previously taken in, and then it will call itself. If the line of input it takes is the designated 'terminator' ( ":done\\n" in this case) then it will write all the concatenated input into a file.

So this does work, but I wanted to know if there was a better way, as this is a considerably more complicated program vs one that reads a file into the terminal, which was just two lines or so really.

The code:

# Mission: find a way to accept multiple lines of input from the
# terminal at once, and write the input into an md file 'my_file.md'.

# Naive approach: read line-by-line in a loop until a terminator is
# reached.

defmodule Term2file do
  def get_input() do
    IO.gets("> ")
    |> get_line()
  end

  defp get_line(line, data \\ "") do
    case line do
      ":done\n" ->
        File.write!("./my_file.md", data, [:append])
      _ ->
        new_data = data <> line
        get_line(IO.gets("> "), new_data)
    end
  end
end

Term2file.get_input()

In an ideal world I would be able to accept input in a similar fashion to Discord (or some another message service), where newlines can be entered using shift-enter and using enter normally would 'complete' the message- allowing the entire input to be edited before entry, even across newlines. I think this might be unreasonable however, and feels like it would require a lot of "fighting" the nature of the terminal itself, but perhaps my intuition is wrong.

Regardless, I would like to know if I've over-complicated this solution, and if there is a simpler, or more straightforward/idiomatic approach that I missed.

You can use streams to traverse the I/O device and write to the file:

defmodule Term2file do
  def get_input() do
    IO.stream(:stdio, :line)
    |> Stream.take_while(& &1 != ":done\n")
    |> Enum.into(File.stream!("./my_file.md", [:append]))
  end
end

Term2file.get_input()

This means you will write to the file as lines are entered. You also don't need to worry about managing the resources. The only downside is that you can't have a custom prompt (a PR that adds this an option though would be appreciated).

In an ideal world I would be able to accept input in a similar fashion to Discord (or some another message service), where newlines can be entered using shift-enter and using enter normally would 'complete' the message

AFAIK, this is not possible since shift-enter and enter are not distinct in any way to a terminal? But you can use Ctrl+D to send a EOF instruction, causing the IO.stream to terminate. This just works in the snippet above but you can also change your program to handle the Ctrl+D output accordingly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM