Skip to content

Commit f51fe48

Browse files
committed
Add io_ansi:render/2 with chardata and no format
1 parent e7e8491 commit f51fe48

1 file changed

Lines changed: 102 additions & 60 deletions

File tree

lib/stdlib/src/io_ansi.erl

Lines changed: 102 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ that should handle it. `io_ansi:fwrite/4` works across nodes and will use the
8686

8787
-export([tput/1, tput/2, tigetnum/1, tigetflag/1, tinfo/0]).
8888
-export([format/1, format/2, format/3, fwrite/1, fwrite/2, fwrite/3, fwrite/4,
89-
enabled/0, enabled/1, scan/1]).
89+
enabled/0, enabled/1, scan/1, render/1, render/2]).
9090

9191
-export([black/0, blue/0, cyan/0, green/0, magenta/0, red/0, white/0, yellow/0,
9292
color/1, color/3, default_color/0]).
@@ -2243,67 +2243,51 @@ lookup_vts(Data) ->
22432243
KeyValueRest
22442244
end.
22452245

2246-
-doc #{ equiv => format(Format, []) }.
2247-
-spec format(format()) -> unicode:unicode_binary().
2248-
format(Format) ->
2249-
format(Format, []).
2250-
2251-
-doc #{ equiv => format(Format, Data, []) }.
2252-
-spec format(format(), Data :: [term()]) -> unicode:unicode_binary().
2253-
format(Format, Data) ->
2254-
format(Format, Data, []).
2246+
-doc #{ equiv => render(Data, []) }.
2247+
-spec render([vts() | unicode:chardata()]) -> unicode:chardata().
2248+
render(Data) ->
2249+
render(Data, []).
22552250

22562251
-doc """
2257-
Returns a character list that represents `Data` formatted in accordance with
2258-
`Format`.
2252+
Renders terminal sequences in `Data`.
22592253

2260-
This function works just as `io_lib:bformat/2`, except that it also allows
2261-
atoms and tuples represeting virtual terminal sequences as part of the
2262-
`Format` string.
2254+
`Data` may contain either virtual terminal sequences, which are rendered,
2255+
or `unicode:chardata()`, which are left as is.
22632256

2264-
Calling `format/3` will always emit a `reset/0` VTS at the end of the returned
2265-
string. To not emit this, set the `reset` option to `false`.
2266-
2267-
To force enabling or disabling of emitting VTSs set the `enabled` option to
2268-
`true` or `false`. By default the emitting of VTSs is enabled if `enabled/0` returns `true`
2269-
and disabled otherwise.
2270-
2271-
To disable emitting of color VTSs but still emit other VTSs, set the `color` option to `false`.
2272-
The default color option is `true` unless the `NO_COLOR` environment variable is set to a non-empty value,
2273-
in which case the default is `false`.
2257+
It accepts the same options as `format/3`.
22742258

22752259
Example:
22762260

22772261
```erlang
2278-
1> io_ansi:format([blue, underline, "Hello world"]).
2279-
~"\e[34m\e[4mHello world\e(B\e[m"
2280-
2> io_ansi:format([blue, underline, "Hello ~p"],[world]).
2281-
~"\e[34m\e[4mHello world\e(B\e[m"
2282-
3> io_ansi:format([blue, underline, "Hello ~p"],[world],[{reset,false}]).
2283-
~"\e[34m\e[4mHello world"
2284-
4> io_ansi:format([blue, underline, "Hello ~p"],[world],[{enabled,false}]).
2285-
~"Hello world"
2286-
5> io_ansi:format([blue, underline, "Hello ~p"],[world],[{color,false}]).
2287-
~"\e[4mHello world\e(B\e[m"
2288-
6> io_ansi:format([invalid_code, "Hello world"]).
2262+
1> io_ansi:render([blue, underline, "Hello world"]).
2263+
[~"\e[34m",~"\e[4m","Hello world",~"\e(B\e[m"]
2264+
2> io_ansi:render([blue, underline, ~"Hello world"],[{reset,false}]).
2265+
[~"\e[34m",~"\e[4m",~"Hello world"]
2266+
3> io_ansi:render([blue, underline, ~"Hello world"],[{enabled,false}]).
2267+
[~"Hello world"]
2268+
4> io_ansi:render([blue, underline, "Hello ", $\n, ~"world"],[{color,false}]).
2269+
[~"\e[4m","Hello ", $\n, ~"world",~"\e(B\e[m"]
2270+
5> io_ansi:render([invalid_code, "Hello world"]).
22892271
** exception error: {invalid_code,invalid_code}
2290-
in function io_ansi:format_internal/3
2272+
in function io_ansi:render_internal/5
22912273
```
2292-
2293-
For a detailed description of the available formatting options, see `io:fwrite/3`.
22942274
""".
2295-
-spec format(format(), Data :: [term()], options()) -> unicode:unicode_binary().
2296-
format(Format, Data, Options) ->
2297-
format_internal(Format, Data, Options).
2275+
-spec render([vts() | unicode:chardata()], options()) -> unicode:chardata().
2276+
render(Data, Options) ->
2277+
render_internal(Data, [], Options, false, fun
2278+
(Fmt, []) when is_list(Fmt); is_binary(Fmt); is_integer(Fmt) ->
2279+
{Fmt, []};
2280+
2281+
(_, _) ->
2282+
erlang:error(badarg, [Data, Options])
2283+
end).
22982284

2299-
format_internal(Format, Data, Options) ->
2285+
render_internal(Format, InitAcc, Options, FormatOnly, Callback) ->
23002286
UseAnsi = case proplists:get_value(enabled, Options) of
23012287
undefined -> enabled();
23022288
Enabled -> Enabled
23032289
end,
2304-
%% Only to be used by fwrite
2305-
FormatOnly = proplists:get_value(format_only, Options, false),
2306-
AppendReset = [reset || proplists:get_value(reset, Options, true)],
2290+
AppendReset = proplists:get_value(reset, Options, true),
23072291
NoColor = os:getenv("NO_COLOR"),
23082292
DefaultColor = NoColor =:= false orelse NoColor =:= "",
23092293
Color = proplists:get_value(color, Options, DefaultColor),
@@ -2323,20 +2307,19 @@ format_internal(Format, Data, Options) ->
23232307
not RenderAnsi ->
23242308
{Acc, Args}
23252309
end;
2326-
(Ansi, {Acc, Args}) when is_atom(Ansi); is_tuple(Ansi) ->
2327-
{Acc, Args};
23282310
(Fmt, {Acc, Args}) ->
2329-
{Scanned, Rest} = io_lib_format:scan(Fmt, Args),
2330-
{[io_lib_format:build_bin(Scanned) | Acc], Rest}
2331-
end, {[], Data}, group([Format,AppendReset])) of
2311+
{Entry, Rest} = Callback(Fmt, Args),
2312+
{[Entry | Acc], Rest}
2313+
end, {[], InitAcc}, Format) of
23322314
{Scanned, []} ->
2333-
if FormatOnly ->
2334-
lists:flatten(lists:reverse(Scanned));
2335-
not FormatOnly ->
2336-
unicode:characters_to_binary(lists:reverse(Scanned))
2337-
end;
2315+
Result = if
2316+
UseAnsi andalso AppendReset -> [reset() | Scanned];
2317+
true -> Scanned
2318+
end,
2319+
2320+
lists:reverse(Result);
23382321
_ ->
2339-
erlang:error(badarg, [Format, Data, Options])
2322+
erlang:error(badarg, [Format, InitAcc, Options])
23402323
catch throw:{invalid_code, Code, []} ->
23412324
erlang:error({invalid_code, Code});
23422325
throw:{invalid_code, Code, Args} ->
@@ -2355,6 +2338,67 @@ is_color(AnsiKey) ->
23552338
lists:member(AnsiKey, ColorAtoms ++ [color, background_color, underline_color,
23562339
default_color, default_background, default_underline_color]).
23572340

2341+
-doc #{ equiv => format(Format, []) }.
2342+
-spec format(format()) -> unicode:unicode_binary().
2343+
format(Format) ->
2344+
format(Format, []).
2345+
2346+
-doc #{ equiv => format(Format, Data, []) }.
2347+
-spec format(format(), Data :: [term()]) -> unicode:unicode_binary().
2348+
format(Format, Data) ->
2349+
format(Format, Data, []).
2350+
2351+
-doc """
2352+
Returns a character list that represents `Data` formatted in accordance with
2353+
`Format`.
2354+
2355+
This function works just as `io_lib:bformat/2`, where `Data` is a list of strings
2356+
as well as atoms and tuples representing virtual terminal sequences as part of the
2357+
`Format` string.
2358+
2359+
Calling `format/3` will always emit a `reset/0` VTS at the end of the returned
2360+
string. To not emit this, set the `reset` option to `false`.
2361+
2362+
To force enabling or disabling of emitting VTSs set the `enabled` option to
2363+
`true` or `false`. By default the emitting of VTSs is enabled if `enabled/0` returns `true`
2364+
and disabled otherwise.
2365+
2366+
To disable emitting of color VTSs but still emit other VTSs, set the `color` option to `false`.
2367+
The default color option is `true` unless the `NO_COLOR` environment variable is set to a non-empty value,
2368+
in which case the default is `false`.
2369+
2370+
Example:
2371+
2372+
```erlang
2373+
1> io_ansi:format([blue, underline, "Hello world"]).
2374+
~"\e[34m\e[4mHello world\e(B\e[m"
2375+
2> io_ansi:format([blue, underline, "Hello ~p"],[world]).
2376+
~"\e[34m\e[4mHello world\e(B\e[m"
2377+
3> io_ansi:format([blue, underline, "Hello ~p"],[world],[{reset,false}]).
2378+
~"\e[34m\e[4mHello world"
2379+
4> io_ansi:format([blue, underline, "Hello ~p"],[world],[{enabled,false}]).
2380+
~"Hello world"
2381+
5> io_ansi:format([blue, underline, "Hello ~p"],[world],[{color,false}]).
2382+
~"\e[4mHello world\e(B\e[m"
2383+
```
2384+
2385+
For a detailed description of the available formatting options, see `io:fwrite/3`.
2386+
""".
2387+
-spec format(format(), Data :: [term()], options()) -> unicode:unicode_binary().
2388+
format(Format, Data, Options) ->
2389+
Formatted = format_internal(group(Format), Data, Options, false),
2390+
unicode:characters_to_binary(Formatted).
2391+
2392+
format_internal(Format, Data, Options, FormatOnly) ->
2393+
render_internal(group(Format), Data, Options, FormatOnly, fun
2394+
(Fmt, Args) when is_list(Fmt) ->
2395+
{Scanned, Rest} = io_lib_format:scan(Fmt, Args),
2396+
{io_lib_format:build_bin(Scanned), Rest};
2397+
2398+
(_, _) ->
2399+
erlang:error(badarg, [Format, Data, Options])
2400+
end).
2401+
23582402
-doc #{ equiv => fwrite(standard_io, Format, [], []) }.
23592403
-spec fwrite(Format :: format()) -> ok.
23602404
fwrite(Format) ->
@@ -2389,8 +2433,6 @@ ok
23892433
ok
23902434
3> io_ansi:fwrite([invalid_code, "%% Hello ~p\n"], [world]).
23912435
** exception error: {error,{put_ansi,unicode,invalid_code}}
2392-
in function io_ansi:fwrite/4
2393-
called as io_ansi:fwrite(standard_io,[invalid_code,"%% Hello ~p\n"],[world],[])
23942436
```
23952437
23962438
The decision what each VTS should be converted to is done by the destination I/O
@@ -2428,7 +2470,7 @@ fwrite(Device, Format, Data, Options) ->
24282470
end;
24292471
F(_Data, Error) ->
24302472
throw({Ref, Error})
2431-
end, ok, format_internal(Format, Data, [{format_only, true} | Options]))
2473+
end, ok, lists:flatten(format_internal(Format, Data, Options, true)))
24322474
catch {Ref, Error} ->
24332475
erlang:error(Error, [Device, Format, Data, Options])
24342476
end.

0 commit comments

Comments
 (0)