|
|
@ -1,869 +0,0 @@ |
|
|
|
%% |
|
|
|
%% @doc Module to print out terms for logging. Limits by length rather than depth. |
|
|
|
%% |
|
|
|
%% The resulting string may be slightly larger than the limit; the intention |
|
|
|
%% is to provide predictable CPU and memory consumption for formatting |
|
|
|
%% terms, not produce precise string lengths. |
|
|
|
%% |
|
|
|
%% Typical use: |
|
|
|
%% |
|
|
|
%% trunc_io:print(Term, 500). |
|
|
|
%% |
|
|
|
%% Source license: Erlang Public License. |
|
|
|
%% Original author: Matthias Lang, <tt>matthias@corelatus.se</tt> |
|
|
|
%% |
|
|
|
%% Various changes to this module, most notably the format/3 implementation |
|
|
|
%% were added by Andrew Thompson `<andrew@basho.com>'. The module has been renamed |
|
|
|
%% to avoid conflicts with the vanilla module. |
|
|
|
|
|
|
|
-module(rumTruncIo). |
|
|
|
-author('matthias@corelatus.se'). |
|
|
|
%% And thanks to Chris Newcombe for a bug fix |
|
|
|
-export([format/3, format/4, print/2, print/3, fprint/2, fprint/3, safe/2]). % interface functions |
|
|
|
-version("$Id: trunc_io.erl,v 1.11 2009-02-23 12:01:06 matthias Exp $"). |
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
|
-export([perf/0, perf/3, perf1/0, test/0, test/2]). % testing functions |
|
|
|
-include_lib("eunit/include/eunit.hrl"). |
|
|
|
-endif. |
|
|
|
|
|
|
|
-type option() :: {'depth', integer()} |
|
|
|
| {'lists_as_strings', boolean()} |
|
|
|
| {'force_strings', boolean()}. |
|
|
|
-type options() :: [option()]. |
|
|
|
|
|
|
|
-record(print_options, { |
|
|
|
%% negative depth means no depth limiting |
|
|
|
depth = -1 :: integer(), |
|
|
|
%% whether to print lists as strings, if possible |
|
|
|
lists_as_strings = true :: boolean(), |
|
|
|
%% force strings, or binaries to be printed as a string, |
|
|
|
%% even if they're not printable |
|
|
|
force_strings = false :: boolean() |
|
|
|
}). |
|
|
|
|
|
|
|
format(Fmt, Args, Max) -> |
|
|
|
format(Fmt, Args, Max, []). |
|
|
|
|
|
|
|
format(Fmt, Args, Max, Options) -> |
|
|
|
try rumFormat:format(Fmt, Args, Max, Options) |
|
|
|
catch |
|
|
|
_What:_Why -> |
|
|
|
erlang:error(badarg, [Fmt, Args]) |
|
|
|
end. |
|
|
|
|
|
|
|
%% @doc Returns an flattened list containing the ASCII representation of the given |
|
|
|
%% term. |
|
|
|
-spec fprint(term(), pos_integer()) -> string(). |
|
|
|
fprint(Term, Max) -> |
|
|
|
fprint(Term, Max, []). |
|
|
|
|
|
|
|
|
|
|
|
%% @doc Returns an flattened list containing the ASCII representation of the given |
|
|
|
%% term. |
|
|
|
-spec fprint(term(), pos_integer(), options()) -> string(). |
|
|
|
fprint(T, Max, Options) -> |
|
|
|
{L, _} = print(T, Max, prepare_options(Options, #print_options{})), |
|
|
|
lists:flatten(L). |
|
|
|
|
|
|
|
%% @doc Same as print, but never crashes. |
|
|
|
%% |
|
|
|
%% This is a tradeoff. Print might conceivably crash if it's asked to |
|
|
|
%% print something it doesn't understand, for example some new data |
|
|
|
%% type in a future version of Erlang. If print crashes, we fall back |
|
|
|
%% to io_lib to format the term, but then the formatting is |
|
|
|
%% depth-limited instead of length limited, so you might run out |
|
|
|
%% memory printing it. Out of the frying pan and into the fire. |
|
|
|
%% |
|
|
|
-spec safe(term(), pos_integer()) -> {string(), pos_integer()} | {string()}. |
|
|
|
safe(What, Len) -> |
|
|
|
case catch print(What, Len) of |
|
|
|
{L, Used} when is_list(L) -> {L, Used}; |
|
|
|
_ -> {"unable to print" ++ io_lib:write(What, 99)} |
|
|
|
end. |
|
|
|
|
|
|
|
%% @doc Returns {List, Length} |
|
|
|
-spec print(term(), pos_integer()) -> {iolist(), pos_integer()}. |
|
|
|
print(Term, Max) -> |
|
|
|
print(Term, Max, []). |
|
|
|
|
|
|
|
%% @doc Returns {List, Length} |
|
|
|
-spec print(term(), pos_integer(), options() | #print_options{}) -> {iolist(), pos_integer()}. |
|
|
|
print(Term, Max, Options) when is_list(Options) -> |
|
|
|
%% need to convert the proplist to a record |
|
|
|
print(Term, Max, prepare_options(Options, #print_options{})); |
|
|
|
|
|
|
|
print(Term, _Max, #print_options{force_strings = true}) when not is_list(Term), not is_binary(Term), not is_atom(Term) -> |
|
|
|
erlang:error(badarg); |
|
|
|
|
|
|
|
print(_, Max, _Options) when Max < 0 -> {"...", 3}; |
|
|
|
print(_, _, #print_options{depth = 0}) -> {"...", 3}; |
|
|
|
|
|
|
|
|
|
|
|
%% @doc We assume atoms, floats, funs, integers, PIDs, ports and refs never need |
|
|
|
%% to be truncated. This isn't strictly true, someone could make an |
|
|
|
%% arbitrarily long bignum. Let's assume that won't happen unless someone |
|
|
|
%% is being malicious. |
|
|
|
%% |
|
|
|
print(Atom, _Max, #print_options{force_strings = NoQuote}) when is_atom(Atom) -> |
|
|
|
L = atom_to_list(Atom), |
|
|
|
R = case atom_needs_quoting_start(L) andalso not NoQuote of |
|
|
|
true -> lists:flatten([$', L, $']); |
|
|
|
false -> L |
|
|
|
end, |
|
|
|
{R, length(R)}; |
|
|
|
|
|
|
|
print(<<>>, _Max, #print_options{depth = 1}) -> |
|
|
|
{"<<>>", 4}; |
|
|
|
print(Bin, _Max, #print_options{depth = 1}) when is_binary(Bin) -> |
|
|
|
{"<<...>>", 7}; |
|
|
|
print(<<>>, _Max, Options) -> |
|
|
|
case Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
{"", 0}; |
|
|
|
false -> |
|
|
|
{"<<>>", 4} |
|
|
|
end; |
|
|
|
|
|
|
|
print(Binary, 0, _Options) when is_bitstring(Binary) -> |
|
|
|
{"<<..>>", 6}; |
|
|
|
|
|
|
|
print(Bin, Max, _Options) when is_binary(Bin), Max < 2 -> |
|
|
|
{"<<...>>", 7}; |
|
|
|
print(Binary, Max, Options) when is_binary(Binary) -> |
|
|
|
B = binary_to_list(Binary, 1, lists:min([Max, byte_size(Binary)])), |
|
|
|
{Res, Length} = |
|
|
|
case Options#print_options.lists_as_strings orelse Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
Depth = Options#print_options.depth, |
|
|
|
MaxSize = (Depth - 1) * 4, |
|
|
|
%% check if we need to truncate based on depth |
|
|
|
In = |
|
|
|
case Depth > -1 andalso MaxSize < length(B) andalso |
|
|
|
not Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
string:substr(B, 1, MaxSize); |
|
|
|
false -> B |
|
|
|
end, |
|
|
|
MaxLen = |
|
|
|
case Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
Max; |
|
|
|
false -> |
|
|
|
%% make room for the leading doublequote |
|
|
|
Max - 1 |
|
|
|
end, |
|
|
|
try alist(In, MaxLen, Options) of |
|
|
|
{L0, Len0} -> |
|
|
|
case Options#print_options.force_strings of |
|
|
|
false -> |
|
|
|
case B /= In of |
|
|
|
true -> |
|
|
|
{[$", L0, "..."], Len0 + 4}; |
|
|
|
false -> |
|
|
|
{[$" | L0], Len0 + 1} |
|
|
|
end; |
|
|
|
true -> |
|
|
|
{L0, Len0} |
|
|
|
end |
|
|
|
catch |
|
|
|
throw:{unprintable, C} -> |
|
|
|
Index = string:chr(In, C), |
|
|
|
case Index > 1 andalso Options#print_options.depth =< Index andalso |
|
|
|
Options#print_options.depth > -1 andalso |
|
|
|
not Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
%% print first Index-1 characters followed by ... |
|
|
|
{L0, Len0} = alist_start(string:substr(In, 1, Index - 1), Max - 1, Options), |
|
|
|
{L0 ++ "...", Len0 + 3}; |
|
|
|
false -> |
|
|
|
list_body(In, Max - 4, dec_depth(Options), true) |
|
|
|
end |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
list_body(B, Max - 4, dec_depth(Options), true) |
|
|
|
end, |
|
|
|
case Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
{Res, Length}; |
|
|
|
_ -> |
|
|
|
{["<<", Res, ">>"], Length + 4} |
|
|
|
end; |
|
|
|
|
|
|
|
%% bitstrings are binary's evil brother who doesn't end on an 8 bit boundary. |
|
|
|
%% This makes printing them extremely annoying, so list_body/list_bodyc has |
|
|
|
%% some magic for dealing with the output of bitstring_to_list, which returns |
|
|
|
%% a list of integers (as expected) but with a trailing binary that represents |
|
|
|
%% the remaining bits. |
|
|
|
print({inline_bitstring, B}, _Max, _Options) when is_bitstring(B) -> |
|
|
|
Size = bit_size(B), |
|
|
|
<<Value:Size>> = B, |
|
|
|
ValueStr = integer_to_list(Value), |
|
|
|
SizeStr = integer_to_list(Size), |
|
|
|
{[ValueStr, $:, SizeStr], length(ValueStr) + length(SizeStr) + 1}; |
|
|
|
print(BitString, Max, Options) when is_bitstring(BitString) -> |
|
|
|
BL = |
|
|
|
case byte_size(BitString) > Max of |
|
|
|
true -> |
|
|
|
binary_to_list(BitString, 1, Max); |
|
|
|
_ -> |
|
|
|
R = erlang:bitstring_to_list(BitString), |
|
|
|
{Bytes, [Bits]} = lists:splitwith(fun erlang:is_integer/1, R), |
|
|
|
%% tag the trailing bits with a special tuple we catch when |
|
|
|
%% list_body calls print again |
|
|
|
Bytes ++ [{inline_bitstring, Bits}] |
|
|
|
end, |
|
|
|
{X, Len0} = list_body(BL, Max - 4, dec_depth(Options), true), |
|
|
|
{["<<", X, ">>"], Len0 + 4}; |
|
|
|
|
|
|
|
print(Float, _Max, _Options) when is_float(Float) -> |
|
|
|
%% use the same function io_lib:format uses to print floats |
|
|
|
%% float_to_list is way too verbose. |
|
|
|
L = io_lib_format:fwrite_g(Float), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print(Fun, Max, _Options) when is_function(Fun) -> |
|
|
|
L = erlang:fun_to_list(Fun), |
|
|
|
case length(L) > Max of |
|
|
|
true -> |
|
|
|
S = erlang:max(5, Max), |
|
|
|
Res = string:substr(L, 1, S) ++ "..>", |
|
|
|
{Res, length(Res)}; |
|
|
|
_ -> |
|
|
|
{L, length(L)} |
|
|
|
end; |
|
|
|
|
|
|
|
print(Integer, _Max, _Options) when is_integer(Integer) -> |
|
|
|
L = integer_to_list(Integer), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print(Pid, _Max, _Options) when is_pid(Pid) -> |
|
|
|
L = pid_to_list(Pid), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print(Ref, _Max, _Options) when is_reference(Ref) -> |
|
|
|
L = erlang:ref_to_list(Ref), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print(Port, _Max, _Options) when is_port(Port) -> |
|
|
|
L = erlang:port_to_list(Port), |
|
|
|
{L, length(L)}; |
|
|
|
|
|
|
|
print({'$lager_record', Name, Fields}, Max, Options) -> |
|
|
|
Leader = "#" ++ atom_to_list(Name) ++ "{", |
|
|
|
{RC, Len} = record_fields(Fields, Max - length(Leader) + 1, dec_depth(Options)), |
|
|
|
{[Leader, RC, "}"], Len + length(Leader) + 1}; |
|
|
|
|
|
|
|
print(Tuple, Max, Options) when is_tuple(Tuple) -> |
|
|
|
{TC, Len} = tuple_contents(Tuple, Max - 2, Options), |
|
|
|
{[${, TC, $}], Len + 2}; |
|
|
|
|
|
|
|
print(List, Max, Options) when is_list(List) -> |
|
|
|
case Options#print_options.lists_as_strings orelse |
|
|
|
Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
alist_start(List, Max, dec_depth(Options)); |
|
|
|
_ -> |
|
|
|
{R, Len} = list_body(List, Max - 2, dec_depth(Options), false), |
|
|
|
{[$[, R, $]], Len + 2} |
|
|
|
end; |
|
|
|
|
|
|
|
print(Map, Max, Options) when is_map(Map) -> |
|
|
|
{MapBody, Len} = map_body(Map, Max - 3, dec_depth(Options)), |
|
|
|
{[$#, ${, MapBody, $}], Len + 3}; |
|
|
|
|
|
|
|
print(Term, Max, Options) -> |
|
|
|
error(badarg, [Term, Max, Options]). |
|
|
|
|
|
|
|
%% Returns {List, Length} |
|
|
|
tuple_contents(Tuple, Max, Options) -> |
|
|
|
L = tuple_to_list(Tuple), |
|
|
|
list_body(L, Max, dec_depth(Options), true). |
|
|
|
|
|
|
|
%% Format the inside of a list, i.e. do not add a leading [ or trailing ]. |
|
|
|
%% Returns {List, Length} |
|
|
|
list_body([], _Max, _Options, _Tuple) -> {[], 0}; |
|
|
|
list_body(_, Max, _Options, _Tuple) when Max < 4 -> {"...", 3}; |
|
|
|
list_body(_, _Max, #print_options{depth = 0}, _Tuple) -> {"...", 3}; |
|
|
|
list_body([H], Max, Options = #print_options{depth = 1}, _Tuple) -> |
|
|
|
print(H, Max, Options); |
|
|
|
list_body([H | _], Max, Options = #print_options{depth = 1}, Tuple) -> |
|
|
|
{List, Len} = print(H, Max - 4, Options), |
|
|
|
Sep = |
|
|
|
case Tuple of |
|
|
|
true -> $,; |
|
|
|
false -> $| |
|
|
|
end, |
|
|
|
{[List ++ [Sep | "..."]], Len + 4}; |
|
|
|
list_body([H | T], Max, Options, Tuple) -> |
|
|
|
{List, Len} = print(H, Max, Options), |
|
|
|
{Final, FLen} = list_bodyc(T, Max - Len, Options, Tuple), |
|
|
|
{[List | Final], FLen + Len}; |
|
|
|
list_body(X, Max, Options, _Tuple) -> %% improper list |
|
|
|
{List, Len} = print(X, Max - 1, Options), |
|
|
|
{[$|, List], Len + 1}. |
|
|
|
|
|
|
|
list_bodyc([], _Max, _Options, _Tuple) -> {[], 0}; |
|
|
|
list_bodyc(_, Max, _Options, _Tuple) when Max < 5 -> {",...", 4}; |
|
|
|
list_bodyc(_, _Max, #print_options{depth = 1}, true) -> {",...", 4}; |
|
|
|
list_bodyc(_, _Max, #print_options{depth = 1}, false) -> {"|...", 4}; |
|
|
|
list_bodyc([H | T], Max, #print_options{depth = Depth} = Options, Tuple) -> |
|
|
|
{List, Len} = print(H, Max, dec_depth(Options)), |
|
|
|
{Final, FLen} = list_bodyc(T, Max - Len - 1, dec_depth(Options), Tuple), |
|
|
|
Sep = |
|
|
|
case Depth == 1 andalso not Tuple of |
|
|
|
true -> $|; |
|
|
|
_ -> $, |
|
|
|
end, |
|
|
|
{[Sep, List | Final], FLen + Len + 1}; |
|
|
|
list_bodyc(X, Max, Options, _Tuple) -> %% improper list |
|
|
|
{List, Len} = print(X, Max - 1, Options), |
|
|
|
{[$|, List], Len + 1}. |
|
|
|
|
|
|
|
map_body(Map, Max, #print_options{depth = Depth}) when Max < 4; Depth =:= 0 -> |
|
|
|
case erlang:map_size(Map) of |
|
|
|
0 -> {[], 0}; |
|
|
|
_ -> {"...", 3} |
|
|
|
end; |
|
|
|
map_body(Map, Max, Options) -> |
|
|
|
case maps:to_list(Map) of |
|
|
|
[] -> |
|
|
|
{[], 0}; |
|
|
|
[{Key, Value} | Rest] -> |
|
|
|
{KeyStr, KeyLen} = print(Key, Max - 4, Options), |
|
|
|
DiffLen = KeyLen + 4, |
|
|
|
{ValueStr, ValueLen} = print(Value, Max - DiffLen, Options), |
|
|
|
DiffLen2 = DiffLen + ValueLen, |
|
|
|
{Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)), |
|
|
|
{[KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen} |
|
|
|
end. |
|
|
|
|
|
|
|
map_bodyc([], _Max, _Options) -> |
|
|
|
{[], 0}; |
|
|
|
map_bodyc(_Rest, Max, #print_options{depth = Depth}) when Max < 5; Depth =:= 0 -> |
|
|
|
{",...", 4}; |
|
|
|
map_bodyc([{Key, Value} | Rest], Max, Options) -> |
|
|
|
{KeyStr, KeyLen} = print(Key, Max - 5, Options), |
|
|
|
DiffLen = KeyLen + 5, |
|
|
|
{ValueStr, ValueLen} = print(Value, Max - DiffLen, Options), |
|
|
|
DiffLen2 = DiffLen + ValueLen, |
|
|
|
{Final, FLen} = map_bodyc(Rest, Max - DiffLen2, dec_depth(Options)), |
|
|
|
{[$,, KeyStr, " => ", ValueStr | Final], DiffLen2 + FLen}. |
|
|
|
|
|
|
|
%% The head of a list we hope is ascii. Examples: |
|
|
|
%% |
|
|
|
%% [65,66,67] -> "ABC" |
|
|
|
%% [65,0,67] -> "A"[0,67] |
|
|
|
%% [0,65,66] -> [0,65,66] |
|
|
|
%% [65,b,66] -> "A"[b,66] |
|
|
|
%% |
|
|
|
alist_start([], _Max, #print_options{force_strings = true}) -> {"", 0}; |
|
|
|
alist_start([], _Max, _Options) -> {"[]", 2}; |
|
|
|
alist_start(_, Max, _Options) when Max < 4 -> {"...", 3}; |
|
|
|
alist_start(_, _Max, #print_options{depth = 0}) -> {"[...]", 5}; |
|
|
|
alist_start(L, Max, #print_options{force_strings = true} = Options) -> |
|
|
|
alist(L, Max, Options); |
|
|
|
%alist_start([H|_T], _Max, #print_options{depth=1}) when is_integer(H) -> {[$[, H, $|, $., $., $., $]], 7}; |
|
|
|
alist_start([H | T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable |
|
|
|
try alist([H | T], Max - 1, Options) of |
|
|
|
{L, Len} -> |
|
|
|
{[$" | L], Len + 1} |
|
|
|
catch |
|
|
|
throw:{unprintable, _} -> |
|
|
|
{R, Len} = list_body([H | T], Max - 2, Options, false), |
|
|
|
{[$[, R, $]], Len + 2} |
|
|
|
end; |
|
|
|
alist_start([H | T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable |
|
|
|
try alist([H | T], Max - 1, Options) of |
|
|
|
{L, Len} -> |
|
|
|
{[$" | L], Len + 1} |
|
|
|
catch |
|
|
|
throw:{unprintable, _} -> |
|
|
|
{R, Len} = list_body([H | T], Max - 2, Options, false), |
|
|
|
{[$[, R, $]], Len + 2} |
|
|
|
end; |
|
|
|
alist_start([H | T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H =:= $\f; H =:= $\b -> |
|
|
|
try alist([H | T], Max - 1, Options) of |
|
|
|
{L, Len} -> |
|
|
|
{[$" | L], Len + 1} |
|
|
|
catch |
|
|
|
throw:{unprintable, _} -> |
|
|
|
{R, Len} = list_body([H | T], Max - 2, Options, false), |
|
|
|
{[$[, R, $]], Len + 2} |
|
|
|
end; |
|
|
|
alist_start(L, Max, Options) -> |
|
|
|
{R, Len} = list_body(L, Max - 2, Options, false), |
|
|
|
{[$[, R, $]], Len + 2}. |
|
|
|
|
|
|
|
alist([], _Max, #print_options{force_strings = true}) -> {"", 0}; |
|
|
|
alist([], _Max, _Options) -> {"\"", 1}; |
|
|
|
alist(_, Max, #print_options{force_strings = true}) when Max < 4 -> {"...", 3}; |
|
|
|
alist(_, Max, #print_options{force_strings = false}) when Max < 5 -> {"...\"", 4}; |
|
|
|
alist([H | T], Max, Options = #print_options{force_strings = false, lists_as_strings = true}) when H =:= $"; H =:= $\\ -> |
|
|
|
%% preserve escaping around quotes |
|
|
|
{L, Len} = alist(T, Max - 1, Options), |
|
|
|
{[$\\, H | L], Len + 2}; |
|
|
|
alist([H | T], Max, Options) when is_integer(H), H >= 16#20, H =< 16#7e -> % definitely printable |
|
|
|
{L, Len} = alist(T, Max - 1, Options), |
|
|
|
{[H | L], Len + 1}; |
|
|
|
alist([H | T], Max, Options) when is_integer(H), H >= 16#a0, H =< 16#ff -> % definitely printable |
|
|
|
{L, Len} = alist(T, Max - 1, Options), |
|
|
|
{[H | L], Len + 1}; |
|
|
|
alist([H | T], Max, Options) when H =:= $\t; H =:= $\n; H =:= $\r; H =:= $\v; H =:= $\e; H =:= $\f; H =:= $\b -> |
|
|
|
{L, Len} = alist(T, Max - 1, Options), |
|
|
|
case Options#print_options.force_strings of |
|
|
|
true -> |
|
|
|
{[H | L], Len + 1}; |
|
|
|
_ -> |
|
|
|
{[escape(H) | L], Len + 1} |
|
|
|
end; |
|
|
|
alist([H | T], Max, #print_options{force_strings = true} = Options) when is_integer(H) -> |
|
|
|
{L, Len} = alist(T, Max - 1, Options), |
|
|
|
{[H | L], Len + 1}; |
|
|
|
alist([H | T], Max, Options = #print_options{force_strings = true}) when is_binary(H); is_list(H) -> |
|
|
|
{List, Len} = print(H, Max, Options), |
|
|
|
case (Max - Len) =< 0 of |
|
|
|
true -> |
|
|
|
%% no more room to print anything |
|
|
|
{List, Len}; |
|
|
|
false -> |
|
|
|
%% no need to decrement depth, as we're in printable string mode |
|
|
|
{Final, FLen} = alist(T, Max - Len, Options), |
|
|
|
{[List | Final], FLen + Len} |
|
|
|
end; |
|
|
|
alist(_, _, #print_options{force_strings = true}) -> |
|
|
|
erlang:error(badarg); |
|
|
|
alist([H | _L], _Max, _Options) -> |
|
|
|
throw({unprintable, H}); |
|
|
|
alist(H, _Max, _Options) -> |
|
|
|
%% improper list |
|
|
|
throw({unprintable, H}). |
|
|
|
|
|
|
|
%% is the first character in the atom alphabetic & lowercase? |
|
|
|
atom_needs_quoting_start([H | T]) when H >= $a, H =< $z -> |
|
|
|
atom_needs_quoting(T); |
|
|
|
atom_needs_quoting_start(_) -> |
|
|
|
true. |
|
|
|
|
|
|
|
atom_needs_quoting([]) -> |
|
|
|
false; |
|
|
|
atom_needs_quoting([H | T]) when (H >= $a andalso H =< $z); |
|
|
|
(H >= $A andalso H =< $Z); |
|
|
|
(H >= $0 andalso H =< $9); |
|
|
|
H == $@; H == $_ -> |
|
|
|
atom_needs_quoting(T); |
|
|
|
atom_needs_quoting(_) -> |
|
|
|
true. |
|
|
|
|
|
|
|
-spec prepare_options(options(), #print_options{}) -> #print_options{}. |
|
|
|
prepare_options([], Options) -> |
|
|
|
Options; |
|
|
|
prepare_options([{depth, Depth} | T], Options) when is_integer(Depth) -> |
|
|
|
prepare_options(T, Options#print_options{depth = Depth}); |
|
|
|
prepare_options([{lists_as_strings, Bool} | T], Options) when is_boolean(Bool) -> |
|
|
|
prepare_options(T, Options#print_options{lists_as_strings = Bool}); |
|
|
|
prepare_options([{force_strings, Bool} | T], Options) when is_boolean(Bool) -> |
|
|
|
prepare_options(T, Options#print_options{force_strings = Bool}). |
|
|
|
|
|
|
|
dec_depth(#print_options{depth = Depth} = Options) when Depth > 0 -> |
|
|
|
Options#print_options{depth = Depth - 1}; |
|
|
|
dec_depth(Options) -> |
|
|
|
Options. |
|
|
|
|
|
|
|
escape($\t) -> "\\t"; |
|
|
|
escape($\n) -> "\\n"; |
|
|
|
escape($\r) -> "\\r"; |
|
|
|
escape($\e) -> "\\e"; |
|
|
|
escape($\f) -> "\\f"; |
|
|
|
escape($\b) -> "\\b"; |
|
|
|
escape($\v) -> "\\v". |
|
|
|
|
|
|
|
record_fields([], _, _) -> |
|
|
|
{"", 0}; |
|
|
|
record_fields(_, Max, #print_options{depth = D}) when Max < 4; D == 0 -> |
|
|
|
{"...", 3}; |
|
|
|
record_fields([{Field, Value} | T], Max, Options) -> |
|
|
|
{ExtraChars, Terminator} = case T of |
|
|
|
[] -> |
|
|
|
{1, []}; |
|
|
|
_ -> |
|
|
|
{2, ","} |
|
|
|
end, |
|
|
|
{FieldStr, FieldLen} = print(Field, Max - ExtraChars, Options), |
|
|
|
{ValueStr, ValueLen} = print(Value, Max - (FieldLen + ExtraChars), Options), |
|
|
|
{Final, FLen} = record_fields(T, Max - (FieldLen + ValueLen + ExtraChars), dec_depth(Options)), |
|
|
|
{[FieldStr ++ "=" ++ ValueStr ++ Terminator | Final], FLen + FieldLen + ValueLen + ExtraChars}. |
|
|
|
|
|
|
|
|
|
|
|
-ifdef(TEST). |
|
|
|
%%-------------------- |
|
|
|
%% The start of a test suite. So far, it only checks for not crashing. |
|
|
|
-spec test() -> ok. |
|
|
|
test() -> |
|
|
|
test(trunc_io, print). |
|
|
|
|
|
|
|
-spec test(atom(), atom()) -> ok. |
|
|
|
test(Mod, Func) -> |
|
|
|
Simple_items = [atom, 1234, 1234.0, {tuple}, [], [list], "string", self(), |
|
|
|
<<1, 2, 3>>, make_ref(), fun() -> ok end], |
|
|
|
F = fun(A) -> |
|
|
|
Mod:Func(A, 100), |
|
|
|
Mod:Func(A, 2), |
|
|
|
Mod:Func(A, 20) |
|
|
|
end, |
|
|
|
|
|
|
|
G = fun(A) -> |
|
|
|
case catch F(A) of |
|
|
|
{'EXIT', _} -> exit({failed, A}); |
|
|
|
_ -> ok |
|
|
|
end |
|
|
|
end, |
|
|
|
|
|
|
|
lists:foreach(G, Simple_items), |
|
|
|
|
|
|
|
Tuples = [{1, 2, 3, a, b, c}, {"abc", def, 1234}, |
|
|
|
{{{{a}, b, c, {d}, e}}, f}], |
|
|
|
|
|
|
|
Lists = [[1, 2, 3, 4, 5, 6, 7], lists:seq(1, 1000), |
|
|
|
[{a}, {a, b}, {a, [b, c]}, "def"], [a | b], [$a | $b]], |
|
|
|
|
|
|
|
|
|
|
|
lists:foreach(G, Tuples), |
|
|
|
lists:foreach(G, Lists). |
|
|
|
|
|
|
|
-spec perf() -> ok. |
|
|
|
perf() -> |
|
|
|
{New, _} = timer:tc(trunc_io, perf, [trunc_io, print, 1000]), |
|
|
|
{Old, _} = timer:tc(trunc_io, perf, [io_lib, write, 1000]), |
|
|
|
io:fwrite("New code took ~p us, old code ~p\n", [New, Old]). |
|
|
|
|
|
|
|
-spec perf(atom(), atom(), integer()) -> done. |
|
|
|
perf(M, F, Reps) when Reps > 0 -> |
|
|
|
test(M, F), |
|
|
|
perf(M, F, Reps - 1); |
|
|
|
perf(_, _, _) -> |
|
|
|
done. |
|
|
|
|
|
|
|
%% Performance test. Needs a particularly large term I saved as a binary... |
|
|
|
-spec perf1() -> {non_neg_integer(), non_neg_integer()}. |
|
|
|
perf1() -> |
|
|
|
{ok, Bin} = file:read_file("bin"), |
|
|
|
A = binary_to_term(Bin), |
|
|
|
{N, _} = timer:tc(trunc_io, print, [A, 1500]), |
|
|
|
{M, _} = timer:tc(io_lib, write, [A]), |
|
|
|
{N, M}. |
|
|
|
|
|
|
|
format_test() -> |
|
|
|
%% simple format strings |
|
|
|
?assertEqual("foobar", lists:flatten(format("~s", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~p", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~P", [["foo", $b, $a, $r], 10], 50))), |
|
|
|
?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~w", [["foo", $b, $a, $r]], 50))), |
|
|
|
|
|
|
|
%% complex ones |
|
|
|
?assertEqual(" foobar", lists:flatten(format("~10s", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("f", lists:flatten(format("~1s", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22p", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~22P", [["foo", $b, $a, $r], 10], 50))), |
|
|
|
?assertEqual("**********", lists:flatten(format("~10W", [["foo", $b, $a, $r], 10], 50))), |
|
|
|
?assertEqual("[[102,111,111],98,97,114]", lists:flatten(format("~25W", [["foo", $b, $a, $r], 10], 50))), |
|
|
|
% Note these next two diverge from io_lib:format; the field width is |
|
|
|
% ignored, when it should be used as max line length. |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10p", [["foo", $b, $a, $r]], 50))), |
|
|
|
?assertEqual("[\"foo\",98,97,114]", lists:flatten(format("~10P", [["foo", $b, $a, $r], 10], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
atom_quoting_test() -> |
|
|
|
?assertEqual("hello", lists:flatten(format("~p", [hello], 50))), |
|
|
|
?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))), |
|
|
|
?assertEqual("'Hello world'", lists:flatten(format("~p", ['Hello world'], 50))), |
|
|
|
?assertEqual("hello_world", lists:flatten(format("~p", ['hello_world'], 50))), |
|
|
|
?assertEqual("'node@127.0.0.1'", lists:flatten(format("~p", ['node@127.0.0.1'], 50))), |
|
|
|
?assertEqual("node@nohost", lists:flatten(format("~p", [node@nohost], 50))), |
|
|
|
?assertEqual("abc123", lists:flatten(format("~p", [abc123], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
sane_float_printing_test() -> |
|
|
|
?assertEqual("1.0", lists:flatten(format("~p", [1.0], 50))), |
|
|
|
?assertEqual("1.23456789", lists:flatten(format("~p", [1.23456789], 50))), |
|
|
|
?assertEqual("1.23456789", lists:flatten(format("~p", [1.234567890], 50))), |
|
|
|
?assertEqual("0.3333333333333333", lists:flatten(format("~p", [1 / 3], 50))), |
|
|
|
?assertEqual("0.1234567", lists:flatten(format("~p", [0.1234567], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
float_inside_list_test() -> |
|
|
|
?assertEqual("[97,38.233913133184835,99]", lists:flatten(format("~p", [[$a, 38.233913133184835, $c]], 50))), |
|
|
|
?assertError(badarg, lists:flatten(format("~s", [[$a, 38.233913133184835, $c]], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
quote_strip_test() -> |
|
|
|
?assertEqual("\"hello\"", lists:flatten(format("~p", ["hello"], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format("~s", ["hello"], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format("~s", [hello], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format("~p", [hello], 50))), |
|
|
|
?assertEqual("'hello world'", lists:flatten(format("~p", ['hello world'], 50))), |
|
|
|
?assertEqual("hello world", lists:flatten(format("~s", ['hello world'], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
binary_printing_test() -> |
|
|
|
?assertEqual("<<>>", lists:flatten(format("~p", [<<>>], 50))), |
|
|
|
?assertEqual("", lists:flatten(format("~s", [<<>>], 50))), |
|
|
|
?assertEqual("<<..>>", lists:flatten(format("~p", [<<"hi">>], 0))), |
|
|
|
?assertEqual("<<...>>", lists:flatten(format("~p", [<<"hi">>], 1))), |
|
|
|
?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<$h, $e, $l, $l, $o>>], 50))), |
|
|
|
?assertEqual("<<\"hello\">>", lists:flatten(format("~p", [<<"hello">>], 50))), |
|
|
|
?assertEqual("<<104,101,108,108,111>>", lists:flatten(format("~w", [<<"hello">>], 50))), |
|
|
|
?assertEqual("<<1,2,3,4>>", lists:flatten(format("~p", [<<1, 2, 3, 4>>], 50))), |
|
|
|
?assertEqual([1, 2, 3, 4], lists:flatten(format("~s", [<<1, 2, 3, 4>>], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format("~s", [<<"hello">>], 50))), |
|
|
|
?assertEqual("hello\nworld", lists:flatten(format("~s", [<<"hello\nworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))), |
|
|
|
?assertEqual("<<\"\\\"hello world\\\"\">>", lists:flatten(format("~p", [<<"\"hello world\"">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\\\world\">>", lists:flatten(format("~p", [<<"hello\\world">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\world">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\\\\\\\world\">>", lists:flatten(format("~p", [<<"hello\\\\world">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\bworld\">>", lists:flatten(format("~p", [<<"hello\bworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\tworld\">>", lists:flatten(format("~p", [<<"hello\tworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\nworld\">>", lists:flatten(format("~p", [<<"hello\nworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\rworld\">>", lists:flatten(format("~p", [<<"hello\rworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\eworld\">>", lists:flatten(format("~p", [<<"hello\eworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\fworld\">>", lists:flatten(format("~p", [<<"hello\fworld">>], 50))), |
|
|
|
?assertEqual("<<\"hello\\vworld\">>", lists:flatten(format("~p", [<<"hello\vworld">>], 50))), |
|
|
|
?assertEqual(" hello", lists:flatten(format("~10s", [<<"hello">>], 50))), |
|
|
|
?assertEqual("[a]", lists:flatten(format("~s", [<<"[a]">>], 50))), |
|
|
|
?assertEqual("[a]", lists:flatten(format("~s", [[<<"[a]">>]], 50))), |
|
|
|
|
|
|
|
ok. |
|
|
|
|
|
|
|
bitstring_printing_test() -> |
|
|
|
?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p", |
|
|
|
[<<1, 2, 3, 1:7>>], 100))), |
|
|
|
?assertEqual("<<1:7>>", lists:flatten(format("~p", |
|
|
|
[<<1:7>>], 100))), |
|
|
|
?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p", |
|
|
|
[<<1, 2, 3, 1:7>>], 12))), |
|
|
|
?assertEqual("<<1,2,3,...>>", lists:flatten(format("~p", |
|
|
|
[<<1, 2, 3, 1:7>>], 13))), |
|
|
|
?assertEqual("<<1,2,3,1:7>>", lists:flatten(format("~p", |
|
|
|
[<<1, 2, 3, 1:7>>], 14))), |
|
|
|
?assertEqual("<<..>>", lists:flatten(format("~p", [<<1:7>>], 0))), |
|
|
|
?assertEqual("<<...>>", lists:flatten(format("~p", [<<1:7>>], 1))), |
|
|
|
?assertEqual("[<<1>>,<<2>>]", lists:flatten(format("~p", [[<<1>>, <<2>>]], |
|
|
|
100))), |
|
|
|
?assertEqual("{<<1:7>>}", lists:flatten(format("~p", [{<<1:7>>}], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
list_printing_test() -> |
|
|
|
?assertEqual("[]", lists:flatten(format("~p", [[]], 50))), |
|
|
|
?assertEqual("[]", lists:flatten(format("~w", [[]], 50))), |
|
|
|
?assertEqual("", lists:flatten(format("~s", [[]], 50))), |
|
|
|
?assertEqual("...", lists:flatten(format("~s", [[]], -1))), |
|
|
|
?assertEqual("[[]]", lists:flatten(format("~p", [[[]]], 50))), |
|
|
|
?assertEqual("[13,11,10,8,5,4]", lists:flatten(format("~p", [[13, 11, 10, 8, 5, 4]], 50))), |
|
|
|
?assertEqual("\"\\rabc\"", lists:flatten(format("~p", [[13, $a, $b, $c]], 50))), |
|
|
|
?assertEqual("[1,2,3|4]", lists:flatten(format("~p", [[1, 2, 3 | 4]], 50))), |
|
|
|
?assertEqual("[...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 4))), |
|
|
|
?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 6))), |
|
|
|
?assertEqual("[1,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 7))), |
|
|
|
?assertEqual("[1,2,...]", lists:flatten(format("~p", [[1, 2, 3, 4]], 8))), |
|
|
|
?assertEqual("[1|4]", lists:flatten(format("~p", [[1 | 4]], 50))), |
|
|
|
?assertEqual("[1]", lists:flatten(format("~p", [[1]], 50))), |
|
|
|
?assertError(badarg, lists:flatten(format("~s", [[1 | 4]], 50))), |
|
|
|
?assertEqual("\"hello...\"", lists:flatten(format("~p", ["hello world"], 10))), |
|
|
|
?assertEqual("hello w...", lists:flatten(format("~s", ["hello world"], 10))), |
|
|
|
?assertEqual("hello world\r\n", lists:flatten(format("~s", ["hello world\r\n"], 50))), |
|
|
|
?assertEqual("\rhello world\r\n", lists:flatten(format("~s", ["\rhello world\r\n"], 50))), |
|
|
|
?assertEqual("\"\\rhello world\\r\\n\"", lists:flatten(format("~p", ["\rhello world\r\n"], 50))), |
|
|
|
?assertEqual("[13,104,101,108,108,111,32,119,111,114,108,100,13,10]", lists:flatten(format("~w", ["\rhello world\r\n"], 60))), |
|
|
|
?assertEqual("...", lists:flatten(format("~s", ["\rhello world\r\n"], 3))), |
|
|
|
?assertEqual("[22835963083295358096932575511191922182123945984,...]", |
|
|
|
lists:flatten(format("~p", [ |
|
|
|
[22835963083295358096932575511191922182123945984, |
|
|
|
22835963083295358096932575511191922182123945984]], 9))), |
|
|
|
?assertEqual("[22835963083295358096932575511191922182123945984,...]", |
|
|
|
lists:flatten(format("~p", [ |
|
|
|
[22835963083295358096932575511191922182123945984, |
|
|
|
22835963083295358096932575511191922182123945984]], 53))), |
|
|
|
%%improper list |
|
|
|
?assertEqual("[1,2,3|4]", lists:flatten(format("~P", [[1 | [2 | [3 | 4]]], 5], 50))), |
|
|
|
?assertEqual("[1|1]", lists:flatten(format("~P", [[1 | 1], 5], 50))), |
|
|
|
?assertEqual("[9|9]", lists:flatten(format("~p", [[9 | 9]], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
iolist_printing_test() -> |
|
|
|
?assertEqual("iolist: HelloIamaniolist", |
|
|
|
lists:flatten(format("iolist: ~s", [[$H, $e, $l, $l, $o, "I", ["am", [<<"an">>], [$i, $o, $l, $i, $s, $t]]]], 1000))), |
|
|
|
?assertEqual("123...", |
|
|
|
lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 6))), |
|
|
|
?assertEqual("123456...", |
|
|
|
lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 9))), |
|
|
|
?assertEqual("123456789H...", |
|
|
|
lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 13))), |
|
|
|
?assertEqual("123456789HellIamaniolist", |
|
|
|
lists:flatten(format("~s", [[<<"123456789">>, "HellIamaniolist"]], 30))), |
|
|
|
|
|
|
|
ok. |
|
|
|
|
|
|
|
tuple_printing_test() -> |
|
|
|
?assertEqual("{}", lists:flatten(format("~p", [{}], 50))), |
|
|
|
?assertEqual("{}", lists:flatten(format("~w", [{}], 50))), |
|
|
|
?assertError(badarg, lists:flatten(format("~s", [{}], 50))), |
|
|
|
?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 1))), |
|
|
|
?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 2))), |
|
|
|
?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 3))), |
|
|
|
?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 4))), |
|
|
|
?assertEqual("{...}", lists:flatten(format("~p", [{foo}], 5))), |
|
|
|
?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo, bar}], 6))), |
|
|
|
?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo, bar}], 7))), |
|
|
|
?assertEqual("{foo,...}", lists:flatten(format("~p", [{foo, bar}], 9))), |
|
|
|
?assertEqual("{foo,bar}", lists:flatten(format("~p", [{foo, bar}], 10))), |
|
|
|
?assertEqual("{22835963083295358096932575511191922182123945984,...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
{22835963083295358096932575511191922182123945984, |
|
|
|
22835963083295358096932575511191922182123945984}], 10))), |
|
|
|
?assertEqual("{22835963083295358096932575511191922182123945984,...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
{22835963083295358096932575511191922182123945984, |
|
|
|
bar}], 10))), |
|
|
|
?assertEqual("{22835963083295358096932575511191922182123945984,...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
{22835963083295358096932575511191922182123945984, |
|
|
|
22835963083295358096932575511191922182123945984}], 53))), |
|
|
|
ok. |
|
|
|
|
|
|
|
map_printing_test() -> |
|
|
|
?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 50))), |
|
|
|
?assertEqual("#{}", lists:flatten(format("~p", [maps:new()], 3))), |
|
|
|
?assertEqual("#{}", lists:flatten(format("~w", [maps:new()], 50))), |
|
|
|
?assertError(badarg, lists:flatten(format("~s", [maps:new()], 50))), |
|
|
|
?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 1))), |
|
|
|
?assertEqual("#{...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 6))), |
|
|
|
?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 7))), |
|
|
|
?assertEqual("#{bar => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 9))), |
|
|
|
?assertEqual("#{bar => foo}", lists:flatten(format("~p", [maps:from_list([{bar, foo}])], 10))), |
|
|
|
?assertEqual("#{bar => ...,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 9))), |
|
|
|
?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 10))), |
|
|
|
?assertEqual("#{bar => foo,...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 17))), |
|
|
|
?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 18))), |
|
|
|
?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 19))), |
|
|
|
?assertEqual("#{bar => foo,foo => ...}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 20))), |
|
|
|
?assertEqual("#{bar => foo,foo => bar}", lists:flatten(format("~p", [maps:from_list([{bar, foo}, {foo, bar}])], 21))), |
|
|
|
?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
maps:from_list([{22835963083295358096932575511191922182123945984, |
|
|
|
22835963083295358096932575511191922182123945984}])], 10))), |
|
|
|
?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
maps:from_list([{22835963083295358096932575511191922182123945984, |
|
|
|
bar}])], 10))), |
|
|
|
?assertEqual("#{22835963083295358096932575511191922182123945984 => ...}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
maps:from_list([{22835963083295358096932575511191922182123945984, |
|
|
|
bar}])], 53))), |
|
|
|
?assertEqual("#{22835963083295358096932575511191922182123945984 => bar}", |
|
|
|
lists:flatten(format("~w", [ |
|
|
|
maps:from_list([{22835963083295358096932575511191922182123945984, |
|
|
|
bar}])], 54))), |
|
|
|
ok. |
|
|
|
|
|
|
|
unicode_test() -> |
|
|
|
?assertEqual([231, 167, 129], lists:flatten(format("~s", [<<231, 167, 129>>], 50))), |
|
|
|
?assertEqual([31169], lists:flatten(format("~ts", [<<231, 167, 129>>], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
depth_limit_test() -> |
|
|
|
?assertEqual("{...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 1], 50))), |
|
|
|
?assertEqual("{a,...}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 2], 50))), |
|
|
|
?assertEqual("{a,[...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 3], 50))), |
|
|
|
?assertEqual("{a,[b|...]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 4], 50))), |
|
|
|
?assertEqual("{a,[b,[...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 5], 50))), |
|
|
|
?assertEqual("{a,[b,[c|...]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 6], 50))), |
|
|
|
?assertEqual("{a,[b,[c,[...]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 7], 50))), |
|
|
|
?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 8], 50))), |
|
|
|
?assertEqual("{a,[b,[c,[d]]]}", lists:flatten(format("~P", [{a, [b, [c, [d]]]}, 9], 50))), |
|
|
|
|
|
|
|
?assertEqual("{a,{...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 3], 50))), |
|
|
|
?assertEqual("{a,{b,...}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 4], 50))), |
|
|
|
?assertEqual("{a,{b,{...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 5], 50))), |
|
|
|
?assertEqual("{a,{b,{c,...}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 6], 50))), |
|
|
|
?assertEqual("{a,{b,{c,{...}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 7], 50))), |
|
|
|
?assertEqual("{a,{b,{c,{d}}}}", lists:flatten(format("~P", [{a, {b, {c, {d}}}}, 8], 50))), |
|
|
|
|
|
|
|
?assertEqual("#{a => #{...}}", |
|
|
|
lists:flatten(format("~P", |
|
|
|
[maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 2], 50))), |
|
|
|
?assertEqual("#{a => #{b => #{...}}}", |
|
|
|
lists:flatten(format("~P", |
|
|
|
[maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 3], 50))), |
|
|
|
?assertEqual("#{a => #{b => #{c => d}}}", |
|
|
|
lists:flatten(format("~P", |
|
|
|
[maps:from_list([{a, maps:from_list([{b, maps:from_list([{c, d}])}])}]), 4], 50))), |
|
|
|
|
|
|
|
?assertEqual("#{}", lists:flatten(format("~P", [maps:new(), 1], 50))), |
|
|
|
?assertEqual("#{...}", lists:flatten(format("~P", [maps:from_list([{1, 1}, {2, 2}, {3, 3}]), 1], 50))), |
|
|
|
?assertEqual("#{1 => 1,...}", lists:flatten(format("~P", [maps:from_list([{1, 1}, {2, 2}, {3, 3}]), 2], 50))), |
|
|
|
?assertEqual("#{1 => 1,2 => 2,...}", lists:flatten(format("~P", [maps:from_list([{1, 1}, {2, 2}, {3, 3}]), 3], 50))), |
|
|
|
?assertEqual("#{1 => 1,2 => 2,3 => 3}", lists:flatten(format("~P", [maps:from_list([{1, 1}, {2, 2}, {3, 3}]), 4], 50))), |
|
|
|
|
|
|
|
?assertEqual("{\"a\",[...]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 3], 50))), |
|
|
|
?assertEqual("{\"a\",[\"b\",[[...]|...]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 6], 50))), |
|
|
|
?assertEqual("{\"a\",[\"b\",[\"c\",[\"d\"]]]}", lists:flatten(format("~P", [{"a", ["b", ["c", ["d"]]]}, 9], 50))), |
|
|
|
|
|
|
|
?assertEqual("[...]", lists:flatten(format("~P", [[1, 2, 3], 1], 50))), |
|
|
|
?assertEqual("[1|...]", lists:flatten(format("~P", [[1, 2, 3], 2], 50))), |
|
|
|
?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, 3], 3], 50))), |
|
|
|
?assertEqual("[1,2,3]", lists:flatten(format("~P", [[1, 2, 3], 4], 50))), |
|
|
|
|
|
|
|
?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))), |
|
|
|
?assertEqual("{1,2,...}", lists:flatten(format("~P", [{1, 2, 3}, 3], 50))), |
|
|
|
?assertEqual("{1,2,3}", lists:flatten(format("~P", [{1, 2, 3}, 4], 50))), |
|
|
|
|
|
|
|
?assertEqual("{1,...}", lists:flatten(format("~P", [{1, 2, 3}, 2], 50))), |
|
|
|
?assertEqual("[1,2|...]", lists:flatten(format("~P", [[1, 2, <<3>>], 3], 50))), |
|
|
|
?assertEqual("[1,2,<<...>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 4], 50))), |
|
|
|
?assertEqual("[1,2,<<3>>]", lists:flatten(format("~P", [[1, 2, <<3>>], 5], 50))), |
|
|
|
|
|
|
|
?assertEqual("<<...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 1], 50))), |
|
|
|
?assertEqual("<<0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 2], 50))), |
|
|
|
?assertEqual("<<0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 3], 50))), |
|
|
|
?assertEqual("<<0,0,0,...>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 4], 50))), |
|
|
|
?assertEqual("<<0,0,0,0>>", lists:flatten(format("~P", [<<0, 0, 0, 0>>, 5], 50))), |
|
|
|
|
|
|
|
%% this is a seriously weird edge case |
|
|
|
?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 2], 50))), |
|
|
|
?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 3], 50))), |
|
|
|
?assertEqual("<<\" \"...>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 4], 50))), |
|
|
|
?assertEqual("<<32,32,32,0>>", lists:flatten(format("~P", [<<32, 32, 32, 0>>, 5], 50))), |
|
|
|
?assertEqual("<<32,32,32,0>>", lists:flatten(format("~p", [<<32, 32, 32, 0>>], 50))), |
|
|
|
|
|
|
|
%% depth limiting for some reason works in 4 byte chunks on printable binaries? |
|
|
|
?assertEqual("<<\"hell\"...>>", lists:flatten(format("~P", [<<"hello world">>, 2], 50))), |
|
|
|
?assertEqual("<<\"abcd\"...>>", lists:flatten(format("~P", [<<$a, $b, $c, $d, $e, 0>>, 2], 50))), |
|
|
|
|
|
|
|
%% I don't even know... |
|
|
|
?assertEqual("<<>>", lists:flatten(format("~P", [<<>>, 1], 50))), |
|
|
|
?assertEqual("<<>>", lists:flatten(format("~W", [<<>>, 1], 50))), |
|
|
|
|
|
|
|
?assertEqual("{abc,<<\"abc\\\"\">>}", lists:flatten(format("~P", [{abc, <<"abc\"">>}, 4], 50))), |
|
|
|
|
|
|
|
ok. |
|
|
|
|
|
|
|
print_terms_without_format_string_test() -> |
|
|
|
?assertError(badarg, format({hello, world}, [], 50)), |
|
|
|
?assertError(badarg, format([{google, bomb}], [], 50)), |
|
|
|
?assertEqual([$h, $e, $l, $l, $o, 3594], format([$h, $e, $l, $l, $o, 3594], [], 50)), |
|
|
|
?assertError(badarg, format([$h, $e, $l, $l, $o, 65535], [], 50)), |
|
|
|
?assertEqual("helloworld", lists:flatten(format([$h, $e, $l, $l, $o, "world"], [], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format(<<"hello">>, [], 50))), |
|
|
|
?assertEqual("hello", lists:flatten(format('hello', [], 50))), |
|
|
|
?assertError(badarg, format(<<1, 2, 3, 1:7>>, [], 100)), |
|
|
|
?assertError(badarg, format(65535, [], 50)), |
|
|
|
ok. |
|
|
|
|
|
|
|
improper_io_list_test() -> |
|
|
|
?assertEqual(">hello", lists:flatten(format('~s', [[$> | <<"hello">>]], 50))), |
|
|
|
?assertEqual(">hello", lists:flatten(format('~ts', [[$> | <<"hello">>]], 50))), |
|
|
|
?assertEqual("helloworld", lists:flatten(format('~ts', [[<<"hello">> | <<"world">>]], 50))), |
|
|
|
ok. |
|
|
|
|
|
|
|
-endif. |