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
59 changes: 0 additions & 59 deletions pkg/cli/display/help.go
Original file line number Diff line number Diff line change
@@ -1,71 +1,12 @@
package display

import (
"strings"

"github.com/innerr/ticat/pkg/core/model"
"github.com/innerr/ticat/pkg/utils"
)

func PrintGlobalHelp(cc *model.Cli, env *model.Env) {
// TODO: use man page instead of help
PrintSelfHelp(cc.Screen, env)
return

if len(cc.Helps.Sections) == 0 {
PrintSelfHelp(cc.Screen, env)
return
}

// TODO: with color output this is not right, disable it by setting to a very big value
_, width := utils.GetTerminalWidth(50, 100)
width = 4096

pln := func(line string) {
line = DecodeColor(line, env)
if len(line) <= width {
_ = cc.Screen.Print(line + "\n")
return
}

indent := 0
for _, char := range line {
if char != ' ' && char != '\t' {
break
}
indent += 1
}
prefix := line[:indent]

printWithPrefix := func(printed bool, line string) {
if printed {
_ = cc.Screen.Print(prefix)
_ = cc.Screen.Print(strings.TrimLeft(line, " \t") + "\n")
} else {
_ = cc.Screen.Print(line + "\n")
}
}

printed := false
for {
if len(line) > width {
printWithPrefix(printed, line[:width])
line = line[width:]
printed = true
continue
} else {
printWithPrefix(printed, line)
}
break
}
}

for _, help := range cc.Helps.Sections {
PrintTipTitle(cc.Screen, env, help.Title)
for _, line := range help.Text {
pln(line)
}
}
}

func PrintSelfHelp(screen model.Screen, env *model.Env) {
Expand Down
57 changes: 4 additions & 53 deletions pkg/mods/builtin/builtin.go
Original file line number Diff line number Diff line change
Expand Up @@ -985,59 +985,10 @@ func RegisterBlenderCmds(cmds *model.CmdTree) {
AddArg("dest", "")

// TODO: disable blenders now, too many bugs
return

remove := cmds.AddSub("remove", "rm", "delete", "del").
RegPowerCmd(BlenderRemoveOnce,
"during executing, remove a command in flow (only remove once)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "")

remove.AddSub("all").
RegPowerCmd(BlenderRemoveForAll,
"during executing, remove a command in flow (remove all)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "")

insert := cmds.AddSub("insert", "ins").
RegPowerCmd(BlenderInsertOnce,
"during executing, insert a command before another matched one (insert once)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "").
AddArg("new", "")

insert.AddSub("all").
RegPowerCmd(BlenderInsertForAll,
"during executing, insert a command before another matched one (insert for all matched)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "").
AddArg("new", "")

after := insert.AddSub("after").
RegPowerCmd(BlenderInsertAfterOnce,
"during executing, insert a command after another matched one (insert once)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "").
AddArg("new", "")

after.AddSub("all").
RegPowerCmd(BlenderInsertAfterForAll,
"during executing, insert a command after another matched one (insert for all matched)").
SetQuiet().
SetIsBlenderCmd().
AddArg("target", "").
AddArg("new", "")

cmds.AddSub("clear", "clean", "reset").
RegPowerCmd(BlenderClear,
"clear all flow modification schedulings").
SetQuiet().
SetIsBlenderCmd()
// The following blender commands are temporarily disabled:
// - blender.remove, blender.remove.all
// - blender.insert, blender.insert.all, blender.insert.after, blender.insert.after.all
// - blender.clear
}

func RegisterCtrlCmds(cmds *model.CmdTree) {
Expand Down
120 changes: 120 additions & 0 deletions pkg/ticat/ticat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2374,3 +2374,123 @@ func TestEnvDeepNestingIntegrity(t *testing.T) {
}
})
}

func TestSubflowEnvIsolation(t *testing.T) {
t.Run("subflow creates separate env layer", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)

tc.Env.Set("test.original", "original-value")

ok := tc.RunCli("env.set", "key=test.new", "value=new-value", ":", "env.assert.equal", "key=test.original", "value=original-value")
if !ok {
t.Error("flow should succeed")
}

if tc.Env.Get("test.new").Raw != "new-value" {
t.Error("new key should be set in session layer")
}
})

t.Run("env changes persist across flow commands", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)

ok := tc.RunCli(
"env.set", "key=persist.a", "value=1",
":", "noop",
":", "env.update", "key=persist.a", "value=2",
":", "noop",
":", "env.assert.equal", "key=persist.a", "value=2")
if !ok {
t.Error("env changes should persist through flow")
}
})
}

func TestNestedSubflowEnv(t *testing.T) {
t.Run("deeply nested flow maintains env integrity", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)

args := []string{"env.set", "key=nested.counter", "value=0", ":"}
for i := 1; i <= 5; i++ {
args = append(args, "env.update", "key=nested.counter", fmt.Sprintf("value=%d", i))
if i < 5 {
args = append(args, ":")
}
}

ok := tc.RunCli(args...)
if !ok {
t.Error("nested flow should succeed")
}

if tc.Env.Get("nested.counter").Raw != "5" {
t.Errorf("counter should be 5, got %s", tc.Env.Get("nested.counter").Raw)
}
})
}

func TestForestModeEnvReset(t *testing.T) {
t.Run("forest mode isolates command env", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)
tc.Env.SetBool("sys.forest-mode", true)

tc.Env.Set("forest.base", "base-value")

ok := tc.RunCli(
"noop",
":", "noop",
":", "env.assert.equal", "key=forest.base", "value=base-value")
if !ok {
t.Error("forest mode flow should succeed")
}
})
}

func TestSubflowFailureEnvHandling(t *testing.T) {
t.Run("env integrity after multiple operations", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)

ok := tc.RunCli(
"env.set", "key=integrity.a", "value=1",
":", "env.set", "key=integrity.b", "value=2",
":", "env.update", "key=integrity.a", "value=1-modified",
":", "env.assert.equal", "key=integrity.a", "value=1-modified",
":", "env.assert.equal", "key=integrity.b", "value=2")
if !ok {
t.Error("env integrity should be maintained")
}
})

t.Run("flow continues after successful operations", func(t *testing.T) {
tc := NewTiCatForTest()
screen := &argsCaptureScreen{}
tc.SetScreen(screen)
tc.Env.SetBool("sys.panic.recover", false)

ok := tc.RunCli(
"env.set", "key=flow.step1", "value=a",
":", "env.set", "key=flow.step2", "value=b",
":", "env.set", "key=flow.step3", "value=c",
":", "env.assert.equal", "key=flow.step1", "value=a",
":", "env.assert.equal", "key=flow.step2", "value=b",
":", "env.assert.equal", "key=flow.step3", "value=c")
if !ok {
t.Error("all flow steps should complete successfully")
}
})
}
Loading