Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion lib/just_bash/commands/echo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ defmodule JustBash.Commands.Echo do

defp parse_flags(rest, flags), do: {flags, rest}

defp interpret_escapes(str) do
# Also used by printf's %b directive, which shares echo -e's escape
# dialect (\0NNN octals, unlike $'...' ANSI-C quoting's \NNN).
@doc false
@spec interpret_escapes(String.t()) :: binary()
def interpret_escapes(str) do
do_interpret_escapes(str, "")
end

Expand Down
16 changes: 14 additions & 2 deletions lib/just_bash/commands/printf.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ defmodule JustBash.Commands.Printf do
@behaviour JustBash.Commands.Command

alias JustBash.Commands.Command
alias JustBash.Commands.Echo

@format_regex ~r/%(-)?(0)?(\d+)?(?:\.(\d+))?([sdxXofec%])/
@format_regex ~r/%(-)?(0)?(\d+)?(?:\.(\d+))?([sdxXofecb%])/

@impl true
def names, do: ["printf"]
Expand Down Expand Up @@ -42,7 +43,14 @@ defmodule JustBash.Commands.Printf do

defp recycle_format(format, args, acc) do
{result, remaining} = apply_formats_with_remaining(format, args)
recycle_format(format, remaining, [result | acc])

if length(remaining) < length(args) do
recycle_format(format, remaining, [result | acc])
else
# A pass that consumed no arguments (an unrecognized directive) must
# not recycle the format forever.
recycle_format(format, [], [result | acc])
end
end

defp apply_formats_with_remaining(format, args) do
Expand Down Expand Up @@ -97,6 +105,10 @@ defmodule JustBash.Commands.Printf do
{padded, []}
end

defp do_format("b", arg, _precision) do
Echo.interpret_escapes(arg)
end

defp do_format("s", arg, precision) do
case precision do
"" -> arg
Expand Down
30 changes: 30 additions & 0 deletions test/commands/utilities_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,36 @@ defmodule JustBash.Commands.UtilitiesTest do
assert result.stdout == "a\tb"
end

test "printf %b expands escape sequences in the argument" do
bash = JustBash.new()
{result, _} = JustBash.exec(bash, ~S[printf "%b" "A\tB\n"])
assert result.stdout == "A\tB\n"
assert result.exit_code == 0
end

@tag timeout: 5_000
test "printf %b with hex escapes emits raw bytes and terminates" do
bash = JustBash.new()
{result, _} = JustBash.exec(bash, ~S[printf "%b" "A\xffB\n"])
assert result.stdout == <<?A, 0xFF, ?B, ?\n>>
end

test "printf %b recycles the format across arguments" do
bash = JustBash.new()
{result, _} = JustBash.exec(bash, ~S[printf "%b\n" "a\tb" "c"])
assert result.stdout == "a\tb\nc\n"
end

# Bash errors on unknown directives; we pass them through literally.
# Either way, a directive that consumes no argument must not recycle
# the format forever.
@tag timeout: 5_000
test "printf with an unrecognized directive terminates instead of looping" do
bash = JustBash.new()
{result, _} = JustBash.exec(bash, ~S[printf "%v\n" x])
assert result.stdout == "%v\n"
end

test "printf with %x hex format" do
bash = JustBash.new()
{result, _} = JustBash.exec(bash, "printf '%x' 255")
Expand Down
Loading