diff --git a/.gitignore b/.gitignore index b618ba9..d5bcf30 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ !/.lua-format !/.githooks/ /output/ +/dist/ diff --git a/changelog.d/20260713_120000_yura_group.md b/changelog.d/20260713_120000_yura_group.md new file mode 100644 index 0000000..dcd3858 --- /dev/null +++ b/changelog.d/20260713_120000_yura_group.md @@ -0,0 +1,8 @@ +### Added + +- `Effect.Console`: `group`, `groupCollapsed`, `groupEnd`, and `grouped`, plus + their `MonadEffect` counterparts in `Effect.Class.Console` — upstream + purescript-console v6.1.0 parity (#268). The Lua FFI keeps an indentation + level: a group prints its label and indents subsequent console output by + two spaces until the matching `groupEnd`; `groupCollapsed` behaves like + `group`, since a terminal cannot collapse anything. diff --git a/src/Effect/Class/Console.purs b/src/Effect/Class/Console.purs index 8f22f8e..d11db86 100644 --- a/src/Effect/Class/Console.purs +++ b/src/Effect/Class/Console.purs @@ -1,5 +1,6 @@ module Effect.Class.Console where +import Control.Bind (discard, bind, pure) import Data.Function ((<<<)) import Data.Show (class Show) import Data.Unit (Unit) @@ -47,3 +48,19 @@ timeEnd = liftEffect <<< EffConsole.timeEnd clear :: forall m. MonadEffect m => m Unit clear = liftEffect EffConsole.clear + +group :: forall m. MonadEffect m => String -> m Unit +group = liftEffect <<< EffConsole.group + +groupCollapsed :: forall m. MonadEffect m => String -> m Unit +groupCollapsed = liftEffect <<< EffConsole.groupCollapsed + +groupEnd :: forall m. MonadEffect m => m Unit +groupEnd = liftEffect EffConsole.groupEnd + +grouped :: forall m a. MonadEffect m => String -> m a -> m a +grouped name inner = do + group name + result <- inner + groupEnd + pure result diff --git a/src/Effect/Console.js b/src/Effect/Console.js index b96fed8..8db1f20 100644 --- a/src/Effect/Console.js +++ b/src/Effect/Console.js @@ -49,3 +49,19 @@ export const timeEnd = function (s) { export const clear = function () { console.clear(); }; + +export const group = function (s) { + return function () { + console.group(s); + }; +}; + +export const groupCollapsed = function (s) { + return function () { + console.groupCollapsed(s); + }; +}; + +export const groupEnd = function () { + console.groupEnd(); +}; diff --git a/src/Effect/Console.lua b/src/Effect/Console.lua index e239ca0..1f019de 100644 --- a/src/Effect/Console.lua +++ b/src/Effect/Console.lua @@ -1,11 +1,29 @@ +-- Grouping (group / groupCollapsed / groupEnd) approximates the JS console +-- semantics in a terminal: group prints its label and indents every following +-- message by two more spaces until the matching groupEnd. A terminal cannot +-- collapse a group, so groupCollapsed behaves exactly like group. +local indentLevel = 0 + +local function indented(s) return string.rep(" ", indentLevel) .. tostring(s) end + +local function openGroup(label) + return function() + print(indented(label)) + indentLevel = indentLevel + 1 + end +end + return { - log = (function(s) return function() print(s) end end), - warn = (function(s) return function() io.stderr:write(tostring(s) .. "\n") end end), - error = (function(s) return function() io.stderr:write(tostring(s) .. "\n") end end), - info = (function(s) return function() print(s) end end), - debug = (function(s) return function() print(s) end end), + log = (function(s) return function() print(indented(s)) end end), + warn = (function(s) return function() io.stderr:write(indented(s) .. "\n") end end), + error = (function(s) return function() io.stderr:write(indented(s) .. "\n") end end), + info = (function(s) return function() print(indented(s)) end end), + debug = (function(s) return function() print(indented(s)) end end), time = (function(_s) return function() error("Sorry, console timers aren't implemented yet") end end), timeLog = (function(_s) return function() error("Sorry, console timers aren't implemented yet") end end), timeEnd = (function(_s) return function() error("Sorry, console timers aren't implemented yet") end end), - clear = (function() io.write("\027[H\027[2J") end) + clear = (function() io.write("\027[H\027[2J") end), + group = (openGroup), + groupCollapsed = (openGroup), + groupEnd = (function() if indentLevel > 0 then indentLevel = indentLevel - 1 end end) } diff --git a/src/Effect/Console.purs b/src/Effect/Console.purs index 9aec0ed..f3d08df 100644 --- a/src/Effect/Console.purs +++ b/src/Effect/Console.purs @@ -1,5 +1,6 @@ module Effect.Console where +import Control.Bind (discard, bind, pure) import Effect (Effect) import Data.Show (class Show, show) @@ -66,3 +67,22 @@ foreign import timeEnd :: String -> Effect Unit -- | Clears the console foreign import clear :: Effect Unit + +-- | Creates a new inline group in the console. This indents following console +-- | messages by an additional level, until `groupEnd` is called. +foreign import group :: String -> Effect Unit + +-- | Same as `group`, but groups are collapsed by default. +foreign import groupCollapsed :: String -> Effect Unit + +-- | Exits the current inline group in the console. +foreign import groupEnd :: Effect Unit + +-- | Perform an effect within the context of an inline group in the console. +-- | Calls `group` and `groupEnd` before and after the effect, respectively. +grouped :: forall a. String -> Effect a -> Effect a +grouped name inner = do + group name + result <- inner + groupEnd + pure result diff --git a/test/regression/console.lua b/test/regression/console.lua index 5be4257..dd168d6 100644 --- a/test/regression/console.lua +++ b/test/regression/console.lua @@ -3,6 +3,10 @@ -- #76 error :: String -> Effect Unit maps to console.error -> stderr in Node; -- the Lua FFI routed it through print (stdout). Now io.stderr:write. -- #77 warn -> console.warn -> stderr, likewise (was print/stdout). +-- #268 group/groupCollapsed/groupEnd were missing from the fork. They keep +-- an indentation level: group prints its label and indents subsequent +-- messages by two spaces per open group; groupCollapsed = group (a +-- terminal cannot collapse); groupEnd at level zero is a no-op. -- log/info/debug correctly stay on stdout (console.log/info/debug all do). -- -- The FFI reads `print` / `io.stderr` as globals at call time, so we swap them @@ -50,5 +54,35 @@ do check("log does not write to stderr", #err == 0, "stderr=" .. table.concat(err)) end +do + local out, err = capture(function() + C.group("outer")() + C.log("one")() + C.groupCollapsed("inner")() + C.warn("two")() + C.groupEnd() + C.log("three")() + C.groupEnd() + C.log("four")() + end) + check("group prints its label at the current indent", out[1] == "outer", "out[1]=" .. tostring(out[1])) + check("log inside a group is indented by two spaces", out[2] == " one", "out[2]=" .. tostring(out[2])) + check("groupCollapsed prints its label like group", out[3] == " inner", "out[3]=" .. tostring(out[3])) + check("warn inside nested groups is indented on stderr", err[1] == " two\n", "err[1]=" .. tostring(err[1])) + check("groupEnd closes the innermost group", out[4] == " three", "out[4]=" .. tostring(out[4])) + check("groupEnd unwinds back to no indent", out[5] == "four", "out[5]=" .. tostring(out[5])) + check("grouping writes nothing extra", #out == 5 and #err == 1, "#out=" .. #out .. " #err=" .. #err) +end + +do + local out = capture(function() + C.groupEnd() -- no open group: must be a no-op, not an indent debt + C.group("g")() + C.log("m")() + C.groupEnd() + end) + check("groupEnd without an open group is a no-op", out[1] == "g" and out[2] == " m", "out=" .. table.concat(out, ",")) +end + if failures > 0 then error(failures .. " regression check(s) failed") end print("purescript-lua-console: all FFI regression checks passed")