fuelen / owl Goto Github PK
View Code? Open in Web Editor NEWA toolkit for writing command-line user interfaces.
License: Apache License 2.0
A toolkit for writing command-line user interfaces.
License: Apache License 2.0
One of the really awesome things that owl
brings to the table is the notion of tags. They're a great primitive that dramatically simplify a lot of things.
An issue I've run up against, however, is that some things break down when you are dealing with iodata that already include ANSI escape sequences. For instance, formatting and syntax-highlighting code using Inspect.Algebra.to_doc/2
and Inspect.Algebra.format/2
does not provide a mechanism other than applying the escape sequences from IO.ANSI
. When this iodata is then used with things like Owl.Box
or Owl.Table
, things break because length calculations for each line assume that data is formatted using tags.
The first time I ran into this, I was able to submit a PR to the library that allowed me to use Owl.Data.tag/2
to colorize, but I think it's pretty well outside the scope of Elixir core to expect a similar option there. π
Add Owl.Data.from_ansidata/1
to convert existing formatted iodata to Owl.Data.t()
.
I would personally be fine with the constraint that the iodata should be "well formatted" in that escape sequences should be separate binaries in a list, not concatenated strings. For example:
iex> ansidata = IO.ANSI.format([:red, "foo"])
[[[[] | "\e[31m"], "foo"], | "\e[0m"]
iex> Owl.Data.from_ansidata(ansidata)
[#Owl.Tag[:red]<"foo">]
iex> Owl.Data.from_ansidata("\e[31mfoo\e[0m")
["\e[31mfoo\e[0m"]
Iβve been unable to find a simple & portable getch
implementation for Elixir. The use case is to detect single keypress events, without needing the return key.
Comparable tools like inquirer/npm and tty/ruby use getch
to build features like type-ahead, pull-down menus, etc.
To implement getch
I've looked at ex_ncurses, rustler, System.cmd, Ports, etc. This is the best Iβve come up with so far:
#!/usr/bin/env elixir
ruby_cmd = ~S"""
ruby -e '
require "io/console"
@output = IO.new(4)
@output.sync = true
char = STDIN.getch
@output.write(char)
'
"""
IO.write("Press a key > ")
port = Port.open({:spawn, ruby_cmd}, [:binary, :nouse_stdio])
receive do
{^port, {:data, result}} -> IO.puts("\nChar: #{result}")
end
This works on my Linux dev machine, but depends on Ruby.
Does anyone know of a portable getch
implementation? Maybe with Rustler or precompiled binaries or ???
I'm trying to print a grid of outputs (think: sudoku) without double-wrapping them into boxes.
I expected this to work:
[
Owl.Data.zip(Owl.Box.new("a b\nc d"), Owl.Box.new("a b\nc d")),
Owl.Data.zip(Owl.Box.new("a b\nc d"), Owl.Box.new("a b\nc d"))
]
|> Owl.IO.puts()
But it displays as something that doesn't paste well here, but the important point is that the last line of the first zip and the first line of the second appear contiguously.
ββββββββββββββββββββ
Really don't want to do a nested box and the only workaround I can find is unrolling my |> and sticking an extra linefeed between the lines.
Owl.Table
is a great tool for making fancy tables, but is a bit too rigid to be used as a general-purpose layout primitive. I'd love some kind of flex-row option that allows layout out multiple data side-by-side, wrapping them down if the terminal width is not sufficient.
Examples:
# Show a side-by-side diff if the terminal width is sufficient. Expand will pad the data so they are the same length.
Owl.Data.flex_row([left_diff, right_diff], expand: true)
# Lay out multiple boxes side-by-side.
Owl.Data.flex_row([box1, box2, box3])
# Lay out multiple boxes side-by-side, wrap all down if terminal width is insufficient
Owl.Data.flex_row([box1, box2, box3], wrap_all: true)
I'd imagine a padding_x
and padding_y
could also be useful, but perhaps gap_x
and gap_y
would be better in this particular case.
My use case is that many spinners are showing the progress of various subtasks, and a "grand total" progress bar shows the overall progress through the full job. I would like the progress bar to stick to the bottom of the screen so that it remains visible, no matter how many subtasks are active.
To do this, it would be ideal if I could create a LiveScreen.add_block and then attach the progress bar to it during .start .
I'm running a concurrent job and each thread has its own progress spinner. What I've found is that the spinners display correctly as long as they're all in the visible terminal area, but once they scroll off-screen then the spinners begin to compete with one another for updates. Instead of updating in-place, each spinner is repeatedly written at the bottom of output as a new line.
Here's a minimal example (terminal must be < 100 rows tall):
1..100
|> Enum.each(fn x ->
Task.async(fn ->
Owl.Spinner.run(
fn ->
Process.sleep(5_000)
:ok
end,
labels: [
ok: "Completed: #{x}",
processing: "Processing: #{x}",
]
)
end)
end)
Process.sleep(5_000)
Thank you for the great library!
Latest versions of Elixir have moved to a copy-pastable Inspect convention. E.g. from MapSet:
iex> MapSet.new([1, :two, {"three"}])
MapSet.new([1, :two, {"three"}])
Tags could do the same:
iex> Owl.Data.tag(βhelloβ, :red)
Owl.Data.tag(βhelloβ, :red)
Happy to implement. :)
Hey, thanks for this library I love building CLI apps with it!
I have written some tests with capture_io
but I can't capture the progress bar output (I'd just like to silence it).
What would be the best way?
If I try to use a string that is too long, I see naive wrapping.
"Very Long Line" |> Owl.Box.new(max_width: 4, border_style: :none)
Very
Lon
g Li
ne
It would be nice if this wrapped to
Very
Long
Line
Any pointers on where to start adding this, and how feasible it might be?
Hi there! First off, thanks very much for making such a useful library. I've been working on a library with a CLI and Owl has been a really big improvement.
I noticed what I assume is a bug in Owl.Box
where consecutive newlines are collapsed to a single newline. I admit that I haven't done further testing to know whether this is actually an issue with some other primitive that Owl.Box
is using, or whether it's specific to the box implementation.
iex(1)> "foo\nbar" |> Owl.Box.new() |> Owl.IO.puts()
βββββ
βfooβ
βbarβ
βββββ
:ok
iex(2)> "foo\n\nbar" |> Owl.Box.new() |> Owl.IO.puts()
βββββ
βfooβ
βbarβ
βββββ
:ok
iex(3)> "foo\n\n\nbar" |> Owl.Box.new() |> Owl.IO.puts()
βββββ
βfooβ
βbarβ
βββββ
:ok
iex(4)> "foo\n \n\nbar" |> Owl.Box.new() |> Owl.IO.puts()
βββββ
βfooβ
β β
βbarβ
βββββ
:ok
Adding additional newline characters does not affect the formatting, unless a space is added (as on iex(4)
), in which case the line with the space will be respected.
This makes it somewhat difficult to insert preformatted text. If you are inserting a single preformatted string, you can get around this by splitting on newlines, mapping over the result and replacing every ""
with " "
before re-joining on newlines, but this is bit of non-obvious extra work. =)
Since it's unclear how far we are from a portable getch, I thought I'd experiment with some way of getting user input while using Owl.LiveScreen
.
After some hacking around, I have a proof of concept that I wanted to share:
The basic idea is to allow Owl.LiveScreen
to render a singleton prompt, and then use \e7
(cursor position save) and \e8
(cursor position restore) to maintain the cursor position during renders. The changes I made in the linked branch are very rough -- basically just getting something working. There are a number of open questions:
\e7
and \e8
? It seems like most modern terminals support them, but they're non-standard, I believe. There's also \e[s
and \e[u
-- there are some notes about them in this gist.:on_input
option to add_block
that can be a function taking the input and block state and returning a new state.LiveScreen.update
replaces the whole state and the owl demo used Stream.iterate
to move the owl, but I wanted the color to react to the user input. For this proof of concept, I just broke the API and decided that LiveScreen.update
would merge the maps so that the owl color wouldn't be overriden by every update from the stream iteration. It may be desirable for LiveScreen.update
to take an update function instead that can be applied to the current state of the block.Weird one here... I'm slightly abusing Owl to render an interface for a MUD that is displayed over a LiveView, and I'm hitting a funny issue when deploying.
The ansi colour codes work fine on dev, when I'm usind a LiveScreen and updating a block, I observe its io_request
message and the colour codes are present.
But when I do this in prod (Debian / Fly.io), the same messages contain no colour codes. I'm observing the raw data in logs:
dev
οΏ½[2KοΏ½[33HelloοΏ½[39m world
prod
οΏ½[2KHello world
(No colour codes)
Any pointers for what might be happening? I've been trying to debug this morning and my best guess is that Owl is doing some kind of terminal support check and disabling colours...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
π Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. πππ
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google β€οΈ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.