diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index a3008d8..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2026 Pt
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/README.md b/README.md
deleted file mode 100644
index 447fe29..0000000
--- a/README.md
+++ /dev/null
@@ -1,308 +0,0 @@
-# Tap - Terminal Argument Parsing
-
-[](https://pkg.go.dev/github.com/pt-main/tap)
-
-```bash
-go get github.com/pt-main/tap
-```
-
-**Tap** is a lightweight library for building beautiful CLI applications in Go.
-It features a simple command-based API, automatic `--flag` parsing, colored output, interactive keyboard dialogs, in-place terminal rewriting, and fully customisable help messages.
-
-## Features
-
-- **Commands** with required / optional arguments and unlimited trailing args
-- **Flag parsing** - `--flag`, `--flag=value`, `--flag:value`
-- **Built‑in colour support** - shortcodes like `[?GN]`, `[?RD]`, `[?YW]` - easy and readable
-- **Background colours & colour stack** - advanced ANSI control for rich layouts
-- **Auto‑generated help** - groups aliases, shows arguments, respects custom format
-- **Fully configurable** - change the look of `help` via `ParserConfig`, add your colors to `color.Colors`, disable colors with `color.ColorEnabled = false`
-- **Hide commands** from help using `DONT_SHOW` docstring
-- **Subcommands** (inner parsers)
-- **Verbose / debug** flags - built‑in `--verbose` and `--debug` with conditional printing
-- **Interactive dialogs** - selection menus (using input/selecting)
-- **In-place terminal rewriting** - update previous output without scrolling
-
-## Quick start
-
-Create a simple CLI with a `hello` command:
-
-```go
-package main
-
-import (
- "fmt"
-
- "github.com/pt-main/tap"
- "github.com/pt-main/tap/color"
-)
-
-func helloHandler(p *tap.Parser, args []string) error {
- color.PrintlnColored("[?GN]Hello[?RT], world! Args: %v", args)
- return nil
-}
-
-func main() {
- cfg := tap.DefaultParserConfig()
- p := tap.NewParser("demo", "Demo CLI v1.0", nil, cfg)
- p.AddCommand("hello", helloHandler, "Prints a friendly greeting", nil, nil, true)
-
- if err := p.Main(); err != nil {
- fmt.Println("Error:", err)
- }
-}
-```
-
-Run it:
-
-
-
-## Commands and arguments
-
-Add a command using `AddCommand`:
-
-```go
-p.AddCommand(
- name string,
- handler HandlerFuncType, // func(*Parser, []string) error
- docstring string,
- requiredArgs []string,
- optionalArgs []string,
- unlimitedMaxArgs bool,
-)
-```
-
-- **requiredArgs** - shown as `` in help. The command fails if they are missing.
-- **optionalArgs** - shown as `[arg]` in help.
-- **unlimitedMaxArgs** - if `true`, the command accepts any number of trailing arguments. shown as `...` in help.
-
-You can create aliases for existing commands using `AddAlias`:
-
-```go
-p.AddCommand("help", helpHandler, tap.HELP_DOCS, nil, nil, false)
-p.AddAlias("h", "help") // now "h" works exactly like "help"
-```
-The alias inherits all properties (handler, arguments, docstring) from the original command.
-
-### Example
-
-```go
-p.AddCommand("copy",
- copyHandler,
- "Copy source to destination",
- []string{"src", "dst"}, // required
- []string{"force"}, // optional
- true, // unlimited
-)
-```
-
-Help output would show:
-```
-copy , , [force]...
-```
-
-Use `tap.DONT_SHOW` as the docstring:
-```go
-p.AddCommand("internal", internalHandler, tap.DONT_SHOW, nil, nil, false)
-```
-This command will work but will never appear in the help output.
-
-Use `tap.DEFAULT_CMD` as name to create handler working when `args[0]` is not command (for usage like `cli_name [args...]`, not `cli_name command_name [args...]`). Documentation of 'default' command shows after about.
-
-## Sub commands
-
-You can create sub-commands:
-
-```go
-p.AddSubcommand(name, tap.NewParser(...))
-```
-
-In call subcomand parser calls `subcommand.Parse(args)`.
-
-Help show only name and about of subcommand.
-
-## Flags
-
-Flags are written as `--flag` or `--key=value` (also `--key:value`).
-They are parsed automatically and stored in `p.Flags` (a `map[string]string`). A flag without a value gets an empty string.
-
-Built‑in flags:
-- `--verbose` - enables verbose output (messages printed with `p.Print("verbose", ...)`)
-- `--debug` - enables debug output (similar)
-
-Your handlers can read flags directly:
-
-```go
-func myHandler(p *tap.Parser, args []string) error {
- if val, ok := p.Flags["output"]; ok {
- fmt.Println("Output file:", val)
- }
- return nil
-}
-```
-
-Use `p.Print(flag, format, ...)` to output messages only when a specific flag (e.g., `--verbose` or `--debug`) is present:
-
-```go
-func myHandler(p *tap.Parser, args []string) error {
- p.Print("verbose", "Starting with args: %v", args)
- p.Print("debug", "Debug info...")
- // ...
-}
-```
-
-The output is automatically prefixed with the flag name and coloured.
-
-## Colors
-
-
-
-You can just write `[?COLOR]` with uppercased color name from list to set color. Like `[?RED]` for red.
-
-All colors: Bold, Underline, Reset, Black, Red, Green, Yellow, Blue, Magenta, Cyan.
-
-Also you can set color using first and last letters of color. Like `[?RD]` for red.
-
-Bright variants: `[?BRED]`, `[?BRD]`, ...
-
-Use them with `color.PrintlnColored` or `color.PrintColored`:
-
-```go
-color.PrintlnColored("[?GN]Success[?RT] - file saved as [?YW]%s[?RT]", filename)
-```
-
-To disable colours globally:
-
-```go
-color.ColorEnabled = false
-```
-
-You can set color to string with `color.Set`:
-```go
-text := color.Set("[?RD]Test")
-```
-(Reset will be auto pasted in the end of text)
-
-### Background colours
-
-Background colours use the `BACK` prefix or `BK` shortcode:
-
-```go
-color.PrintlnColored("[?BKRD] ERROR: [?RT] error text...")
-```
-
-Available all colors for text except bold and underlinne.
-
-### Colour stack
-
-You can restore the previous colour with `[?<]` (or `[?BACK]`) and clear the stack with `[?SRT]` (or `[?SRESET]`):
-
-```go
-color.PrintlnColored("[?BE][?UE]test [?BD]bold [?RT][?<]restored")
-```
-
-
-## Interactive dialogs
-
-Tap includes a small utility for interactive user prompts. It supports arrow-key navigation and validated text input.
-
-```go
-import "github.com/pt-main/tap/utils"
-
-// Arrow-key selection
-dialogue := utils.NewDialogue(utils.ArrowsDialogueType, "[?CN]Select action:[?RT]")
-choice, err := dialogue.Run([]string{"install", "update", "remove"})
-// Navigate with up/down, press enter to confirm, esc to cancel.
-```
-
-For simple typed input with validation against allowed variants:
-
-```go
-inputDlg := utils.NewDialogue(utils.InputDialogueType, "Enter mode (fast/slow): ")
-mode, err := inputDlg.Run([]string{"fast", "slow"})
-```
-
-## Rewriting terminal output
-
-For in-place updates (progress bars, live menus, spinners) use `Rewriter`:
-
-```go
-rw := utils.NewRewriter()
-rw.Write("[?YW]Loading... 0%[?RT]")
-// ... later ...
-rw.Write("[?GN]Loading... 100%[?RT]") // replaces the previous line without scrolling
-```
-
-## Customising the help output
-
-Create a `ParserConfig` and pass it to `NewParser`.
-All fields support format strings - use `%s` for the command name or argument list.
-
-```go
-cfg := tap.NewParserConfig(
- "[?CN]>>> Command [?RT]%s[?CN] <<<[?RT]",
- "[?CN]Args:[?RT]",
- " %s",
- "[?CN]Description:[?RT]",
- " %s",
- "[?CN]---[?RT]",
-)
-p := tap.NewParser("mycli", "My tool", nil, cfg)
-```
-
-If you pass an empty string for any field, the default (coloured, nice looking) will be used.
-
-## Grouping commands / aliases
-
-If multiple commands share the **same docstring**, they are displayed together in help:
-
-```go
-helpDocs := "Show help"
-p.AddCommand("help", helpHandler, helpDocd, nil, nil, false)
-p.AddCommand("h", helpHandler, helpDocd, nil, nil, false) // Equals to p.AddAlias("h", help)
-```
-
-Help shows: `[help / h]`
-
-## Full example
-
-A minimal but complete CLI with multiple commands:
-
-```go
-package main
-
-import (
- "fmt"
- "os"
- "github.com/pt-main/tap"
- "github.com/pt-main/tap/color"
-)
-
-func main() {
- cfg := tap.DefaultParserConfig()
- p := tap.NewParser("myapp", "My application v0.1", nil, cfg)
-
- p.AddCommand("greet", func(p *tap.Parser, args []string) error {
- name := "world"
- if len(args) > 0 {
- name = args[0]
- }
- color.PrintlnColored("[?GN]Hello, %s![?RT]", name)
- return nil
- }, "Say hello", []string{}, []string{"name"}, false)
-
- p.AddCommand("print", func(p *tap.Parser, args []string) error {
- color.PrintlnColored("[?YW]%s[?RT]", args[0])
- return nil
- }, "Print first argument", []string{"text"}, nil, false)
-
- if err := p.Main(); err != nil {
- fmt.Fprintln(os.Stderr, "Fatal:", err)
- os.Exit(1)
- }
-}
-```
-
-## License
-
-MIT - see [LICENSE](LICENSE) file.
diff --git a/colFmt/cmd/main.go b/colFmt/cmd/main.go
new file mode 100644
index 0000000..de4d7a4
--- /dev/null
+++ b/colFmt/cmd/main.go
@@ -0,0 +1,34 @@
+package main
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/pt-main/colFmt/lib"
+ "github.com/pt-main/lc/engine/core"
+)
+
+func main() {
+ if a := os.Args; len(a) > 1 {
+ printErr := func(err error) {
+ fmt.Printf("{%s}{Err=%s}", a[1], err.Error())
+ }
+ l, err := lib.NewLanguage()
+ if err != nil {
+ printErr(err)
+ return
+ }
+ err = l.ProcessString(a[1])
+ if err != nil {
+ printErr(err)
+ return
+ }
+ uep, _ := l.GetUEP()
+ res, err := core.GetStringRes(uep.Generator, "")
+ if err != nil {
+ printErr(err)
+ return
+ }
+ fmt.Print(res)
+ }
+}
diff --git a/colFmt/go.mod b/colFmt/go.mod
new file mode 100644
index 0000000..5bfb027
--- /dev/null
+++ b/colFmt/go.mod
@@ -0,0 +1,9 @@
+module github.com/pt-main/colFmt
+
+go 1.24.13
+
+require (
+ github.com/dlclark/regexp2 v1.12.0 // indirect
+ github.com/pt-main/lc v1.4.2 // indirect
+ github.com/pt-main/tap v1.4.5 // indirect
+)
diff --git a/colFmt/go.sum b/colFmt/go.sum
new file mode 100644
index 0000000..e3c2f6a
--- /dev/null
+++ b/colFmt/go.sum
@@ -0,0 +1,6 @@
+github.com/dlclark/regexp2 v1.12.0 h1:0j4c5qQmnC6XOWNjP3PIXURXN2gWx76rd3KvgdPkCz8=
+github.com/dlclark/regexp2 v1.12.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
+github.com/pt-main/lc v1.4.2 h1:1+UiNubv+7R/p++pDvjvX+7qa1rSvAf5JV1QH4fJwk0=
+github.com/pt-main/lc v1.4.2/go.mod h1:YNbfNwM3eO/xHarctZ288mYFKFv4X9mBsDzpgogByCs=
+github.com/pt-main/tap v1.4.5 h1:g6W1aIx7qHhVmugicqGRvk5UugpyGWDn/ip57iCIK6E=
+github.com/pt-main/tap v1.4.5/go.mod h1:ULQUJ/+8VIji9oq26pr1cmbXv+VUlhjsvq1n/vd4f3I=
diff --git a/colFmt/lib/handlers.go b/colFmt/lib/handlers.go
new file mode 100644
index 0000000..af3dd7b
--- /dev/null
+++ b/colFmt/lib/handlers.go
@@ -0,0 +1,73 @@
+package lib
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/pt-main/lc/engine"
+ "github.com/pt-main/lc/engine/core"
+ "github.com/pt-main/lc/parsing/stringParsing"
+ "github.com/pt-main/tap/color"
+)
+
+func Color(se *engine.StringEngine, pn *stringParsing.ParsedNode) error {
+ i := pn.Metadata["value"].(string)
+ setter, err := core.ScopeGet[*color.Setter](se.UEP.Scope, "setter")
+ if err != nil {
+ return err
+ }
+ s := ""
+ for _, col := range strings.Split(i, ";") {
+ s += "[?" + col + "]"
+ }
+ s = setter.Set(s)
+ return se.UEP.Generator.AddString(s, "main")
+}
+
+func Setup(se *engine.StringEngine, pn *stringParsing.ParsedNode) error {
+ i := pn.Metadata["value"].(string)
+ setter, err := core.ScopeGet[*color.Setter](se.UEP.Scope, "setter")
+ if err != nil {
+ return err
+ }
+ for _, param := range strings.Split(i, ";") {
+ switch param {
+ case "ColEnable", "Color+", "C+":
+ *setter.ColorEnabled = true
+ case "ColDisable", "Color-", "C-":
+ *setter.ColorEnabled = false
+ case "Col", "Color", "C":
+ *setter.ColorEnabled = !(*setter.ColorEnabled)
+ case "AddReset+", "AR+":
+ setter.AddReset = true
+ case "AddReset-", "AR-":
+ setter.AddReset = false
+ case "AddReset", "AR":
+ setter.AddReset = !setter.AddReset
+ default:
+ return fmt.Errorf("Invalid param: %s", param)
+ }
+ }
+ return nil
+}
+
+func Manual(se *engine.StringEngine, pn *stringParsing.ParsedNode) error {
+ s := pn.Metadata["value"].(string)
+ setter, err := core.ScopeGet[*color.Setter](se.UEP.Scope, "setter")
+ if err != nil {
+ return err
+ }
+ if !(*setter.ColorEnabled) {
+ return nil
+ }
+ if len(s) == 0 {
+ s = "0"
+ }
+ s = fmt.Sprintf("\033[%sm", s)
+ return se.UEP.Generator.AddString(s, "main")
+}
+
+func Raw(se *engine.StringEngine, pn *stringParsing.ParsedNode) error {
+ s := pn.Metadata["value"].(string)
+ return se.UEP.Generator.AddString(s, "main")
+}
diff --git a/colFmt/lib/main.go b/colFmt/lib/main.go
new file mode 100644
index 0000000..1780bd2
--- /dev/null
+++ b/colFmt/lib/main.go
@@ -0,0 +1,56 @@
+package lib
+
+import (
+ "github.com/dlclark/regexp2"
+ "github.com/pt-main/lc"
+ "github.com/pt-main/lc/parsing/stringParsing"
+ "github.com/pt-main/lc/public"
+ "github.com/pt-main/tap/color"
+)
+
+func NewLexer() *stringParsing.Lexer {
+ return stringParsing.NewLexer(
+ []stringParsing.LexerRule{
+ {
+ Type: "SETUP",
+ Pattern: regexp2.MustCompile(`\[[sS]/(?[^\]]*)\]`, 0),
+ },
+ {
+ Type: "COLOR",
+ Pattern: regexp2.MustCompile(`\[[cC]/(?[^\]]*)\]`, 0),
+ },
+ {
+ Type: "MANUAL",
+ Pattern: regexp2.MustCompile(`\[[mM]/(?[^\]]*)\]`, 0),
+ },
+ {
+ Type: "RAW",
+ Pattern: regexp2.MustCompile(`(?(?:[^[]|\[(?![sScCmM]/))+)`, 0),
+ },
+ },
+ &stringParsing.LexerConfig{
+ UseBracketBalance: false,
+ },
+ )
+}
+
+func NewLanguage() (*lc.EngineUniversal, error) {
+ e, err := lc.NewEngineBuilder(public.StringEngineType, public.StringResType).
+ WithStringParser(NewLexer()).
+ WithDefaultEvents(true).
+ WithPipeline([]string{"main"}).
+ Build()
+ if err != nil {
+ return nil, err
+ }
+ uep, err := e.GetUEP()
+ if err != nil {
+ return nil, err
+ }
+ uep.Scope["setter"] = color.NewSetter(false, &color.ColorEnabled)
+ e.NewCommandString("SETUP", Setup, "")
+ e.NewCommandString("COLOR", Color, "")
+ e.NewCommandString("MANUAL", Manual, "")
+ e.NewCommandString("RAW", Raw, "")
+ return e, nil
+}
diff --git a/color/colors.go b/color/colors.go
deleted file mode 100644
index b616c75..0000000
--- a/color/colors.go
+++ /dev/null
@@ -1,101 +0,0 @@
-// Package color provides ANSI color formatting for terminal output.
-// It supports short color codes like [?GN] (green), [?RD] (red), [?BOLD], etc.
-// Coloring can be globally disabled by setting color.ColorEnabled = false.
-package color
-
-// Color codes for ANSI escape sequences.
-const (
- black = "\033[30m"
- red = "\033[31m"
- green = "\033[32m"
- yellow = "\033[33m"
- blue = "\033[34m"
- magenta = "\033[35m"
- cyan = "\033[36m"
- white = "\033[37m"
-
- brightBlack = "\033[90m"
- brightRed = "\033[91m"
- brightGreen = "\033[92m"
- brightYellow = "\033[93m"
- brightBlue = "\033[94m"
- brightMagenta = "\033[95m"
- brightCyan = "\033[96m"
- brightWhite = "\033[97m"
-
- // Background colours (standard)
- blackBg = "\033[40m"
- redBg = "\033[41m"
- greenBg = "\033[42m"
- yellowBg = "\033[43m"
- blueBg = "\033[44m"
- magentaBg = "\033[45m"
- cyanBg = "\033[46m"
- whiteBg = "\033[47m"
-
- // Background colours (bright)
- brightBlackBg = "\033[100m"
- brightRedBg = "\033[101m"
- brightGreenBg = "\033[102m"
- brightYellowBg = "\033[103m"
- brightBlueBg = "\033[104m"
- brightMagentaBg = "\033[105m"
- brightCyanBg = "\033[106m"
- brightWhiteBg = "\033[107m"
-
- reset = "\033[0m"
- bold = "\033[1m"
- underline = "\033[4m"
-)
-
-// Colors maps short color codes (uppercase) to ANSI escape sequences.
-// Supported codes:
-// - Text foreground: BOLD/BD, UNDERLINE/UE, RESET/RT,
-// basic colours (BLACK/BK, RED/RD, GREEN/GN, YELLOW/YW, BLUE/BE, MAGENTA/MA, CYAN/CN),
-// bright variants (BBLACK/BBK, BRED/BRD, BGREEN/BGN, BYELLOW/BYW, BBLUE/BBE, BMAGENTA/BMA, BCYAN/BCN)
-// - Background colours (full name: BACK, short: BK):
-// BACKBLACK/BKBK, BACKRED/BKRD, BACKGREEN/BKGN, BACKYELLOW/BKYW, BACKBLUE/BKBE, BACKMAGENTA/BKMA, BACKCYAN/BKCN,
-// and bright variants: BACKBBLACK/BKBBK, BACKBRED/BKBRD, BACKBGREEN/BKBGN, BACKBYELLOW/BKBYW, BACKBBLUE/BKBBE, BACKBMAGENTA/BKBMA, BACKBCYAN/BKBCN.
-var Colors = map[string]string{
- "BOLD": bold, "BD": bold,
- "UNDERLINE": underline, "UE": underline,
- "RESET": reset, "RT": reset,
-
- // Text foreground
- "BLACK": black, "BK": black,
- "RED": red, "RD": red,
- "GREEN": green, "GN": green,
- "YELLOW": yellow, "YW": yellow,
- "BLUE": blue, "BE": blue,
- "MAGENTA": magenta, "MA": magenta,
- "CYAN": cyan, "CN": cyan,
-
- "BBLACK": brightBlack, "BBK": brightBlack,
- "BRED": brightRed, "BRD": brightRed,
- "BGREEN": brightGreen, "BGN": brightGreen,
- "BYELLOW": brightYellow, "BYW": brightYellow,
- "BBLUE": brightBlue, "BBE": brightBlue,
- "BMAGENTA": brightMagenta, "BMA": brightMagenta,
- "BCYAN": brightCyan, "BCN": brightCyan,
-
- // Background colours
- "BACKBLACK": blackBg, "BKBK": blackBg,
- "BACKRED": redBg, "BKRD": redBg,
- "BACKGREEN": greenBg, "BKGN": greenBg,
- "BACKYELLOW": yellowBg, "BKYW": yellowBg,
- "BACKBLUE": blueBg, "BKBE": blueBg,
- "BACKMAGENTA": magentaBg, "BKMA": magentaBg,
- "BACKCYAN": cyanBg, "BKCN": cyanBg,
-
- "BACKBBLACK": brightBlackBg, "BKBBK": brightBlackBg,
- "BACKBRED": brightRedBg, "BKBRD": brightRedBg,
- "BACKBGREEN": brightGreenBg, "BKBGN": brightGreenBg,
- "BACKBYELLOW": brightYellowBg, "BKBYW": brightYellowBg,
- "BACKBBLUE": brightBlueBg, "BKBBE": brightBlueBg,
- "BACKBMAGENTA": brightMagentaBg, "BKBMA": brightMagentaBg,
- "BACKBCYAN": brightCyanBg, "BKBCN": brightCyanBg,
-
- // Other
- "BACK": "", "<": "",
- "SRESET": "", "SRT": "",
-}
diff --git a/color/print.go b/color/print.go
deleted file mode 100644
index 3d2bb70..0000000
--- a/color/print.go
+++ /dev/null
@@ -1,17 +0,0 @@
-package color
-
-import "fmt"
-
-// PrintColored formats the string using fmt.Sprintf, replaces color codes,
-// and writes the result to stdout without a trailing newline.
-func PrintColored(format string, args ...interface{}) {
- formatted := fmt.Sprintf(format, args...)
- fmt.Print(Set(formatted))
-}
-
-// PrintlnColored formats the string using fmt.Sprintf, replaces color codes,
-// and writes the result to stdout followed by a newline.
-func PrintlnColored(format string, args ...interface{}) {
- formatted := fmt.Sprintf(format, args...)
- fmt.Println(Set(formatted))
-}
diff --git a/color/set.go b/color/set.go
deleted file mode 100644
index 551db67..0000000
--- a/color/set.go
+++ /dev/null
@@ -1,83 +0,0 @@
-package color
-
-import (
- "slices"
- "strings"
-)
-
-// ColorEnabled globally enables or disables ANSI color output.
-// When false, all color codes are stripped from the output.
-var ColorEnabled = true
-
-// Replace color codes
-func ReplaceColors(text string) string {
- result := text
- for code, ansi := range Colors {
- if !ColorEnabled {
- ansi = ""
- }
- result = strings.ReplaceAll(result, FormColorPlaceholder(code), ansi)
- }
- return result
-}
-
-// Place color code (like 'RD', 'RED', etc.) to form for replace to color
-// Like 'rd' -> '[?RD]'
-func FormColorPlaceholder(code string) string {
- return "[?" + strings.ToUpper(code) + "]"
-}
-
-// Set replaces short color codes like [?RED] or [?GN] in the input string
-// with the corresponding ANSI escape sequences. If ColorEnabled is false,
-// the codes are removed entirely. The ANSI reset code is automatically
-// appended at the end when colors are enabled.
-//
-// Use 'BACK' (or '<') code to append last colors.
-//
-// Use 'SRESET' (or 'SRT') code to reset color stack.
-//
-// Example:
-//
-// "[?BE][?UE]test [?BD]bold [?RT][?<]string" equal to "[?BE]test [?BD]bold [?RT][?BE][?UE]string"
-func Set(text string) string {
- BackVariants := []string{"BACK", "<"}
- SrtVariants := []string{"SRESET", "SRT"}
- result := text
- if !ColorEnabled {
- return ReplaceColors(result)
- }
- colorStack := []string{}
- for code, ansi := range Colors {
- if slices.Contains(BackVariants, code) {
- replace := ""
- for _, code := range colorStack {
- replace += Colors[code]
- }
- result = strings.Replace(result, code, replace, 1)
- } else if slices.Contains(SrtVariants, code) {
- result = strings.Replace(result, code, "", 1)
- colorStack = []string{}
- } else {
- result = strings.ReplaceAll(result, FormColorPlaceholder(code), ansi)
- if ansi == Colors["RT"] {
- if len(colorStack)-1 >= 0 {
- colorStack = colorStack[:len(colorStack)-1]
- }
- } else {
- colorStack = append(colorStack, code)
- }
- }
-
- }
- return result + Colors["RT"]
-}
-
-// ConvertColored applies color formatting to each string in the slice
-// using Set and returns a new slice with the colored strings.
-func ConvertColored(text ...string) []string {
- result := []string{}
- for arg := range text {
- result = append(result, Set(text[arg]))
- }
- return result
-}
diff --git a/config.go b/config.go
deleted file mode 100644
index aaf73c1..0000000
--- a/config.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Package tap provides a lightweight command-line argument parser with support
-// for commands, flags, colored output, and customizable help messages.
-package tap
-
-// DONT_SHOW is a special docstring value that hides the command from the help output.
-// The command remains functional but will not appear in the auto-generated help.
-const DONT_SHOW = "#[DON'T SHOW]#"
-
-// DEFAULT_CMD is a reserved command name.
-// It is used when no command is specified as the first argument,
-// allowing your CLI to be invoked in two ways: cli args... (implicit default)
-// or cli cmd args... (explicit command).
-// Note: documentation of default command is displayed after about message.
-const DEFAULT_CMD = "#[DEFAULT CMD]#"
-
-// HELP_DOCS is the docstring used by the built‑in help command.
-// Commands sharing this docstring will be grouped together as aliases.
-const HELP_DOCS = "Generate and print help message"
-
-// ParserConfig defines the formatting templates for the auto-generated help message.
-// Each field is a format string that may contain "%s" placeholders for dynamic content.
-// If an empty string is passed to NewParserConfig, the corresponding default format will be used.
-type ParserConfig struct {
- help_command_block_fmt string
- help_subcommand_block_fmt string
- help_args_header_block_fmt string
- help_args_data_block_fmt string
- help_docs_header_block_fmt string
- help_docs_data_block_fmt string
- help_end_block_fmt string
-}
-
-// NewParserConfig creates a new ParserConfig with the given format strings.
-// Any empty string parameter will be replaced with a sensible default.
-// Parameters:
-// - help_command_block_fmt: format for the command name block (e.g., "╭─────── Command [%s]").
-// - help_subcommand_block_fmt: format for the subcommand name block (e.g., "╭─────── Subcommand [%s]").
-// - help_args_header_block_fmt: format for the arguments section header.
-// - help_args_data_block_fmt: format for each argument line.
-// - help_docs_header_block_fmt: format for the description section header.
-// - help_docs_data_block_fmt: format for each line of the description.
-// - help_end_block_fmt: format for the closing block.
-//
-// Returns a populated ParserConfig.
-func NewParserConfig(
- help_command_block_fmt string,
- help_subcommand_block_fmt string,
- help_args_header_block_fmt string,
- help_args_data_block_fmt string,
- help_docs_header_block_fmt string,
- help_docs_data_block_fmt string,
- help_end_block_fmt string,
-) ParserConfig {
- if help_command_block_fmt == "" {
- help_command_block_fmt = "[?GN]╭─────── Command[?RT] [%s]"
- }
- if help_subcommand_block_fmt == "" {
- help_subcommand_block_fmt = "[?GN]╭─────── Subommand[?RT] [%s]"
- }
- if help_args_header_block_fmt == "" {
- help_args_header_block_fmt = "[?GN]⎬─ Args:[?RT]"
- }
- if help_args_data_block_fmt == "" {
- help_args_data_block_fmt = "[?GN]│[?RT] %s"
- }
- if help_docs_header_block_fmt == "" {
- help_docs_header_block_fmt = "[?GN]⎬─ Desc:[?RT]"
- }
- if help_docs_data_block_fmt == "" {
- help_docs_data_block_fmt = "[?GN]│[?RT] %s"
- }
- if help_end_block_fmt == "" {
- help_end_block_fmt = "[?GN]╰───────[?RT]"
- }
-
- return ParserConfig{
- help_command_block_fmt: help_command_block_fmt,
- help_subcommand_block_fmt: help_subcommand_block_fmt,
- help_args_header_block_fmt: help_args_header_block_fmt,
- help_args_data_block_fmt: help_args_data_block_fmt,
- help_docs_header_block_fmt: help_docs_header_block_fmt,
- help_docs_data_block_fmt: help_docs_data_block_fmt,
- help_end_block_fmt: help_end_block_fmt,
- }
-}
-
-// Create default parser config.
-func DefaultParserConfig() ParserConfig {
- return NewParserConfig("", "", "", "", "", "", "")
-}
diff --git a/core/scope.go b/core/scope.go
deleted file mode 100644
index 3ef9526..0000000
--- a/core/scope.go
+++ /dev/null
@@ -1,18 +0,0 @@
-package core
-
-import "errors"
-
-type ScopeType map[string]interface{}
-
-func ScopeGet[T any](st ScopeType, what string) (T, error) {
- var nul T
- val, ok := st[what]
- if !ok {
- return nul, errors.New("Scope.Get: Invalid key: " + what)
- }
- res, ok := val.(T)
- if !ok {
- return nul, errors.New("Scope.Get: Invalid type for key: " + what)
- }
- return res, nil
-}
diff --git a/core/utils.go b/core/utils.go
deleted file mode 100644
index d028dbb..0000000
--- a/core/utils.go
+++ /dev/null
@@ -1,52 +0,0 @@
-package core
-
-import "strings"
-
-// utils is an internal helper type providing argument parsing functionality.
-type Utils struct{}
-
-func (u *Utils) ParseFlag(el string) (string, string) {
- el = el[2:]
-
- var key, value string
- var hasValue bool
-
- if strings.Contains(el, "=") {
- parts := strings.SplitN(el, "=", 2)
- key, value = parts[0], parts[1]
- hasValue = true
- } else if strings.Contains(el, ":") {
- parts := strings.SplitN(el, ":", 2)
- key, value = parts[0], parts[1]
- hasValue = true
- } else {
- key = el
- hasValue = false
- }
-
- if hasValue {
- return key, value
- } else {
- return key, ""
- }
-}
-
-// parse_args processes a slice of command-line arguments.
-// It detects flags prefixed with "--" (e.g., "--flag", "--key=value", "--key:value").
-// Flags without a value are stored with an empty string.
-// Returns a map of flag names to their values, and a slice of non-flag arguments.
-func (u *Utils) ParseArgs(argv []string) (map[string]string, []string) {
- flags := make(map[string]string)
- var result []string
-
- for _, el := range argv {
- if strings.HasPrefix(el, "--") {
- key, val := u.ParseFlag(el)
- flags[key] = val
- } else {
- result = append(result, el)
- }
- }
-
- return flags, result
-}
diff --git a/go.mod b/go.mod
deleted file mode 100644
index c1e5910..0000000
--- a/go.mod
+++ /dev/null
@@ -1,8 +0,0 @@
-module github.com/pt-main/tap
-
-go 1.24.13
-
-require (
- github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 // indirect
- golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
-)
diff --git a/go.sum b/go.sum
deleted file mode 100644
index 24996dc..0000000
--- a/go.sum
+++ /dev/null
@@ -1,4 +0,0 @@
-github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
-github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
-golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/help.go b/help.go
deleted file mode 100644
index c2f2447..0000000
--- a/help.go
+++ /dev/null
@@ -1,91 +0,0 @@
-package tap
-
-import (
- "slices"
- "strings"
-
- "github.com/pt-main/tap/color"
-)
-
-func help_form_args(el command) string {
- if el.optional_args != nil || el.required_args != nil || el.unlimited_max_args {
- args_doc := ""
- if el.required_args != nil && len(el.required_args) != 0 {
- for arg := range el.required_args {
- args_doc += "<[?RD]" + el.required_args[arg] + "[?RT]>"
- if arg != (len(el.required_args) - 1) {
- args_doc += ", "
- }
- }
- }
- if el.optional_args != nil && len(el.optional_args) != 0 {
- if len(args_doc) > 2 {
- args_doc += ", "
- }
- for arg := range el.optional_args {
- args_doc += "[[?BE]" + el.optional_args[arg] + "[?RT]]"
- if arg != (len(el.optional_args) - 1) {
- args_doc += ", "
- }
- }
- }
- if el.unlimited_max_args {
- args_doc += "..."
- }
- return args_doc
- }
- return ""
-}
-
-// help_cmd_handler implements the built‑in help command.
-// It prints a formatted help message listing all visible commands,
-// their arguments (required/optional), and descriptions.
-func help_cmd_handler(p *Parser, _ []string) error {
- p._print_about()
- docstrings := []string{}
- for key := range p._commands {
- el := p._commands[key]
- docs := strings.Split(el.docstring, "\n")
- if el.docstring == DONT_SHOW || el.name == DEFAULT_CMD {
- docstrings = append(docstrings, el.docstring)
- }
- if slices.Index(docstrings, el.docstring) == -1 {
- cmds := []string{}
- for key := range p._commands {
- if p._commands[key].docstring == el.docstring {
- cmds = append(cmds, p._commands[key].name)
- }
- }
- commands := "[?YW]"
- for idx, cmd := range cmds {
- commands += cmd
- if idx != (len(cmds) - 1) {
- commands += " [?RT]/[?YW] "
- }
- }
- commands += "[?RT]"
- color.PrintlnColored(p._config.help_command_block_fmt, commands)
- args_doc := help_form_args(el)
- if args_doc != "" {
- color.PrintlnColored(p._config.help_args_header_block_fmt)
- color.PrintlnColored(p._config.help_args_data_block_fmt, args_doc)
- }
- color.PrintlnColored(p._config.help_docs_header_block_fmt)
- for line := range docs {
- color.PrintlnColored(p._config.help_docs_data_block_fmt, docs[line])
- }
- color.PrintlnColored(p._config.help_end_block_fmt)
- docstrings = append(docstrings, el.docstring)
- }
- }
- for key := range p._sub_commands {
- el := p._sub_commands[key]
- color.PrintlnColored(p._config.help_subcommand_block_fmt, el._cli_name)
- color.PrintlnColored(p._config.help_docs_header_block_fmt)
- for _, line := range strings.Split(el._about_info, "\n") {
- color.PrintlnColored(p._config.help_docs_data_block_fmt, line)
- }
- color.PrintlnColored(p._config.help_end_block_fmt)
- }
- return nil
-}
diff --git a/main.go b/main.go
deleted file mode 100644
index 29be18b..0000000
--- a/main.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package tap
-
-const Version = "1.4.5"
diff --git a/parser.go b/parser.go
deleted file mode 100644
index 37ab22b..0000000
--- a/parser.go
+++ /dev/null
@@ -1,210 +0,0 @@
-package tap
-
-import (
- "errors"
- "fmt"
- "os"
- "strings"
-
- "github.com/pt-main/tap/color"
- "github.com/pt-main/tap/core"
-)
-
-// HandlerFuncType defines the signature for command handler functions.
-// It receives the parser instance and the slice of command arguments.
-// Returns an error if the command execution fails.
-type HandlerFuncType func(*Parser, []string) error
-
-// Command stores internal metadata for a registered command.
-type command struct {
- name string
- handler HandlerFuncType
- docstring string
- required_args []string
- optional_args []string
- unlimited_max_args bool
-}
-
-/*
-# Tap - Terminal Argument Parsing
-
-This parser is the main object in tap.
-
-Main methods:
-
-- AddCommand(name string, handler HandlerFuncType)
-
-- Main() - start parser
-*/
-type Parser struct {
- _cli_name string
- _about_info string
- _parser_flags map[string]bool
- Flags map[string]string
- Scope core.ScopeType
- _commands map[string]command
- _sub_commands map[string]*Parser
- _config ParserConfig
-}
-
-// NewParser creates a new Parser instance.
-// Parameters:
-// - cli_name: name of the CLI application (used in help).
-// - about: informational text printed when no command is given.
-// - help_commands: slice of command names that trigger the help handler.
-// If nil, defaults to []string{"help", "h"}.
-// - config: ParserConfig controlling help message formatting.
-//
-// Returns a pointer to the initialized Parser.
-func NewParser(cli_name string, about string, help_commands []string, config ParserConfig) *Parser {
- p := Parser{
- _cli_name: cli_name,
- _about_info: about,
- _parser_flags: map[string]bool{
- "debug": false, "verbose": false,
- },
- _commands: map[string]command{},
- _sub_commands: make(map[string]*Parser),
- Flags: map[string]string{},
- _config: config,
- }
- if help_commands == nil {
- help_commands = []string{"help", "h"}
- }
- for _, cmd := range help_commands {
- p.AddCommand(cmd, help_cmd_handler, HELP_DOCS, nil, nil, false)
- }
- return &p
-}
-
-// AddCommand registers a new command with the parser.
-// Parameters:
-// - name: command name (string used in CLI).
-// - handler: function called when the command is invoked.
-// - docs: description shown in help; use DONT_SHOW to hide the command.
-// - required_args: slice of required argument names.
-// - optional_args: slice of optional argument names.
-// - unlimited_max_args: if true, command accepts any number of trailing arguments.
-func (p *Parser) AddCommand(
- name string,
- handler HandlerFuncType,
- docs string,
- required_args []string,
- optional_args []string,
- unlimited_max_args bool,
-) {
- p.__print_verbose(
- "Adding command '%s' with %v required and %v optional args",
- name, required_args, optional_args,
- )
- p._commands[name] = command{
- name: name,
- handler: handler,
- docstring: docs,
- required_args: required_args,
- optional_args: optional_args,
- unlimited_max_args: unlimited_max_args,
- }
-}
-
-// AddSubCommand registers a nested sub-parser for a given subcommand name.
-//
-// This method stores the provided Parser instance under the specified name,
-// automatically integrate it into the main command dispatch flow.
-//
-// Parameters:
-// - name: the subcommand name.
-// - parser: the Parser instance that will handle the subcommand.
-func (p *Parser) AddSubcommand(name string, parser *Parser) {
- p._sub_commands[name] = parser
-}
-
-// SubCommand retrieves a previously registered sub-parser by name.
-//
-// It returns the Parser pointer and a nil error if the name exists;
-// otherwise, it returns an error describing the invalid name.
-func (p *Parser) Subcommand(name string) (err error, pr *Parser) {
- var ok bool
- pr, ok = p._sub_commands[name]
- if !ok {
- err = fmt.Errorf("Invalid subcommand name: %v", name)
- return
- }
- return
-}
-
-// Add alias for command.
-func (p *Parser) AddAlias(aliasName, cmdName string) error {
- cmdMap, ok := p._commands[cmdName]
- if !ok {
- return errors.New("[?RD]Can't add alias[?RT]: \nCommand not found")
- }
- p._commands[aliasName] = cmdMap
- return nil
-}
-
-// Print outputs a formatted message only if the given flag (e.g., "debug", "verbose") is enabled.
-// The message can contain color shortcodes. Each newline is prefixed with the flag’s name for alignment.
-func (p *Parser) Print(flag string, format string, args ...any) {
- spaces := strings.Repeat(" ", len(flag))
- format = strings.ReplaceAll(format, "\n", "\n"+spaces+" [?GN]=>[?RT] ")
- if p._parser_flags[flag] {
- color.PrintlnColored("[?RD]"+strings.ToUpper(flag)+"[?RT] [?GN]=>[?RT] "+format, args...)
- }
-}
-
-// Parse args.
-func (p *Parser) Parse(cmdArgs []string) error {
- argv := p._parse_args(cmdArgs)
- p.__check_flags()
- if len(argv) < 1 {
- p._print_about()
- var help_name string
- for _, el := range p._commands {
- if el.docstring == HELP_DOCS {
- help_name = el.name
- break
- }
- }
- color.PrintlnColored(
- "[?RD]Has no command.[?RT] Type [[?YW]%s[?RT]] for help.",
- help_name,
- )
- return errors.New("[?RD]No command provided[?RT]")
- }
- p.__print_verbose("Finding and calling command...")
- cmd := argv[0]
- args := argv[1:]
- p.__print_verbose(
- "Call '%s' with %v args...",
- cmd, args,
- )
- err := p._call_command(cmd, args)
- p.__print_verbose("Return after call: %v", err)
- if err != nil {
- err = p._call_basic(argv)
- if err != nil {
- p.Print("debug", "'%s' cmd handler call with '%s' args end with error: %v", cmd, args, err)
- return fmt.Errorf("[?RD]Command [?BBK]%q[?RD] failed: \n%w", cmd, err)
- }
- }
- return nil
-}
-
-// Main is the primary entry point of the parser.
-// It parses os.Args[1:], extracts flags, finds the command, and executes the corresponding handler.
-// Returns an error if no command is provided, the command is unknown, or the handler fails.
-func (p *Parser) Main() error {
- err := p.Parse(os.Args[1:])
- if err != nil {
- errstart := "[?RT][?YW]->[?RT] [?BBK]|[?RT] "
- return fmt.Errorf(color.Set(
- fmt.Sprintf("[?YW]Execution stopped[?RT]: \n"+errstart+"%v",
- strings.ReplaceAll(
- err.Error(),
- "\n", "\n"+errstart,
- ),
- )))
- }
- return fmt.Errorf("")
-}
diff --git a/systems.go b/systems.go
deleted file mode 100644
index 68ae3dc..0000000
--- a/systems.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package tap
-
-import (
- "fmt"
-
- "github.com/pt-main/tap/color"
- "github.com/pt-main/tap/core"
-)
-
-func isArgsInalid(args []string, cmd command) bool {
- full_length := len(cmd.optional_args) + len(cmd.required_args)
- cond1 := (len(args) > full_length) && (!cmd.unlimited_max_args)
- cond2 := len(args) < len(cmd.required_args)
- return cond1 || cond2
-}
-
-// _call_command looks up the command by name and executes its handler.
-// It validates argument count against required/optional definitions.
-// Returns an error if the command is unknown or argument count is invalid.
-func (p *Parser) _call_command(name string, args []string) error {
- cmd, ok := p._commands[name]
- if !ok {
- if err := p._call_subcommand(name, args); err != nil {
- return err
- }
- }
- if isArgsInalid(args, cmd) {
- return fmt.Errorf("Invalid argument length: %d.", len(args))
- }
- return cmd.handler(p, args)
-}
-
-func (p *Parser) _call_basic(args []string) error {
- cmd, ok := p._commands[DEFAULT_CMD]
- if !ok {
- return fmt.Errorf("Bad input.")
- }
- if isArgsInalid(args, cmd) {
- return fmt.Errorf("Invalid argument length: %d.", len(args))
- }
- return cmd.handler(p, args)
-}
-
-func (p *Parser) _call_subcommand(name string, args []string) error {
- cmd, ok := p._sub_commands[name]
- if !ok {
- return fmt.Errorf("Unknown command: %s", name)
- }
- return cmd.Parse(args)
-}
-
-// _parse_args extracts flags (--flag, --key=value, --key:value) from the raw argument slice.
-// Flags are stored in p.Flags (value is empty string if no value was given).
-// Returns the remaining non‑flag arguments.
-func (p *Parser) _parse_args(argv []string) []string {
- p.__print_verbose("Parsing args.")
- flags, res := (&core.Utils{}).ParseArgs(argv)
- p.Flags = flags
- return res
-}
-
-// __print_verbose prints a formatted message when the "verbose" flag is enabled.
-func (p *Parser) __print_verbose(format string, args ...any) {
- p.Print("verbose", format, args...)
-}
-
-// __print_debug prints a formatted message when the "debug" flag is enabled.
-func (p *Parser) __print_debug(format string, args ...any) {
- p.Print("debug", format, args...)
-}
-
-// _print_about prints the CLI information (name/version) stored in _about_info.
-func (p *Parser) _print_about() {
- p.__print_verbose("Print about")
- color.PrintlnColored(p._about_info)
- println(color.Set(p._commands[DEFAULT_CMD].docstring))
-}
-
-// __check_flags enables internal verbose/debug flags based on presence in p.Flags.
-func (p *Parser) __check_flags() {
- _, verbose_ok := p.Flags["verbose"]
- if verbose_ok {
- p._parser_flags["verbose"] = true
- }
- _, debug_ok := p.Flags["debug"]
- if debug_ok {
- p._parser_flags["debug"] = true
- }
- p.__print_verbose(
- "Check flags by verbose and debug. \n Flags: %v, \n Parser flags: %v",
- p.Flags, p._parser_flags,
- )
-}
diff --git a/tap/Cargo.lock b/tap/Cargo.lock
new file mode 100644
index 0000000..7e87fb1
--- /dev/null
+++ b/tap/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "tap-rs"
+version = "0.3.0"
diff --git a/tap/Cargo.toml b/tap/Cargo.toml
new file mode 100644
index 0000000..ecd59e4
--- /dev/null
+++ b/tap/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "tap-rs"
+version = "0.3.0"
+edition = "2024"
+
+[dependencies]
+
+[lib]
+name = "tap"
+path = "src/tap/mod.rs"
diff --git a/tap/resources/cmd-darwin-amd64-final b/tap/resources/cmd-darwin-amd64-final
new file mode 100755
index 0000000..1814734
Binary files /dev/null and b/tap/resources/cmd-darwin-amd64-final differ
diff --git a/tap/resources/cmd-darwin-arm64-final b/tap/resources/cmd-darwin-arm64-final
new file mode 100755
index 0000000..adb29cd
Binary files /dev/null and b/tap/resources/cmd-darwin-arm64-final differ
diff --git a/tap/resources/cmd-linux-amd64-final b/tap/resources/cmd-linux-amd64-final
new file mode 100755
index 0000000..57444f6
Binary files /dev/null and b/tap/resources/cmd-linux-amd64-final differ
diff --git a/tap/resources/cmd-linux-arm64-final b/tap/resources/cmd-linux-arm64-final
new file mode 100755
index 0000000..974d2a8
Binary files /dev/null and b/tap/resources/cmd-linux-arm64-final differ
diff --git a/tap/resources/cmd-windows-amd64-final.exe b/tap/resources/cmd-windows-amd64-final.exe
new file mode 100755
index 0000000..4c42e51
Binary files /dev/null and b/tap/resources/cmd-windows-amd64-final.exe differ
diff --git a/tap/resources/cmd-windows-arm64-final.exe b/tap/resources/cmd-windows-arm64-final.exe
new file mode 100755
index 0000000..36fadfa
Binary files /dev/null and b/tap/resources/cmd-windows-arm64-final.exe differ
diff --git a/tap/src/lib.rs b/tap/src/lib.rs
new file mode 100644
index 0000000..5444222
--- /dev/null
+++ b/tap/src/lib.rs
@@ -0,0 +1 @@
+pub mod tap;
\ No newline at end of file
diff --git a/tap/src/main.rs b/tap/src/main.rs
new file mode 100644
index 0000000..49cdf87
--- /dev/null
+++ b/tap/src/main.rs
@@ -0,0 +1,12 @@
+use tap::{argsparsing::ArgsP, color, engine::structs::{CmdInfo, Engine}};
+
+fn main() {
+ let mut ap = ArgsP::new();
+ let mut e = Engine::new(&mut ap);
+ e.new_command("cmd", CmdInfo::new(|_, a: &[&str]|{
+ println!("args: {:?}", a);
+ None
+ }, vec![], vec![], true));
+ println!("{:?}, {:?}", e.work(&vec!["--test", "cmd", "args", "--val:test", "-val2=val"]), e.args_p.values);
+ println!("{}", color::set::set("[s/C+][c/GN]test[c/RT]"))
+}
diff --git a/tap/src/tap/argsparsing.rs b/tap/src/tap/argsparsing.rs
new file mode 100644
index 0000000..4e97bcc
--- /dev/null
+++ b/tap/src/tap/argsparsing.rs
@@ -0,0 +1,57 @@
+use std::collections::HashMap;
+
+pub struct ArgsP {
+ pub args: Vec,
+ pub flags: Vec,
+ pub values: HashMap,
+}
+
+impl ArgsP {
+ pub fn new() -> Self {
+ Self {
+ args: vec![],
+ flags: vec![],
+ values: HashMap::new(),
+ }
+ }
+
+ fn count_leading_same_chars(s: &str) -> usize {
+ let mut chars = s.chars();
+ match chars.next() {
+ Some(first) => 1 + chars.take_while(|&c| c == first).count(),
+ None => 0,
+ }
+ }
+
+ fn parse_args(args: &[&str]) -> (Vec, HashMap, Vec) {
+ let mut flags: Vec = vec![];
+ let mut values: HashMap = HashMap::new();
+ let mut r_args: Vec = vec![];
+ for arg in args {
+ let mut split: Vec<&str> = arg.splitn(2, "=").collect();
+ if split.len() == 1 {
+ split = arg.splitn(2, ":").collect();
+ }
+ let s0 = split[0];
+ let sch = Self::count_leading_same_chars(s0);
+
+ if s0.starts_with("-") && s0.len() > sch && sch < 3 {
+ if split.len() > 1 {
+ values.insert(s0[sch..].to_string(), split[1].to_string());
+ } else {
+ flags.push(s0[sch..].to_string());
+ }
+ } else {
+ r_args.push(arg.to_string());
+ }
+ }
+ (flags, values, r_args)
+ }
+
+ pub fn parse_ags_to_self(&mut self, args: &[&str]) {
+ let (flags, values, r_args) = Self::parse_args(args);
+ self.flags = flags;
+ self.values = values;
+ self.args = r_args;
+ }
+}
diff --git a/tap/src/tap/color/mod.rs b/tap/src/tap/color/mod.rs
new file mode 100644
index 0000000..1ba27d5
--- /dev/null
+++ b/tap/src/tap/color/mod.rs
@@ -0,0 +1,2 @@
+pub mod set;
+pub mod print;
\ No newline at end of file
diff --git a/tap/src/tap/color/print.rs b/tap/src/tap/color/print.rs
new file mode 100644
index 0000000..41fbd51
--- /dev/null
+++ b/tap/src/tap/color/print.rs
@@ -0,0 +1,9 @@
+use crate::color::set::set;
+
+pub fn print(text: &str) {
+ print!("{}", set(text));
+}
+
+pub fn println(text: &str) {
+ print(&(text.to_owned() + "\n"))
+}
\ No newline at end of file
diff --git a/tap/src/tap/color/set.rs b/tap/src/tap/color/set.rs
new file mode 100644
index 0000000..dde4b5b
--- /dev/null
+++ b/tap/src/tap/color/set.rs
@@ -0,0 +1,38 @@
+use std::process::Command;
+use std::path::PathBuf;
+use std::env::consts::{OS, ARCH};
+
+fn get_binary_path() -> PathBuf {
+ let os_name = match OS {
+ "windows" => "win",
+ "macos" => "darwin",
+ other => other,
+ };
+
+ let arch_name = match ARCH {
+ "x86_64" => "amd64",
+ "aarch64" => "arm64",
+ other => other,
+ };
+
+ let mut binary_name = format!("cmd-{}-{}-final", os_name, arch_name);
+ if OS == "windows" {
+ binary_name.push_str(".exe");
+ }
+ PathBuf::from("./resources").join(binary_name)
+}
+
+pub fn set(text: &str) -> String {
+ let binary_path = get_binary_path();
+ if !binary_path.exists() {
+ panic!("Binary not found: {:?}", binary_path);
+ }
+ let output = Command::new(&binary_path)
+ .arg(format!("\"{}\"", text.replace("\"", "\\\"")))
+ .output()
+ .expect("failed to execute process");
+
+ let res = String::from_utf8(output.stdout)
+ .expect("output is not valid UTF-8");
+ res[1..(res.len()-1)].to_string()
+}
\ No newline at end of file
diff --git a/tap/src/tap/engine/cmdinfo.rs b/tap/src/tap/engine/cmdinfo.rs
new file mode 100644
index 0000000..2b46a96
--- /dev/null
+++ b/tap/src/tap/engine/cmdinfo.rs
@@ -0,0 +1,17 @@
+use crate::engine::structs::{CmdInfo};
+
+impl<'a, H> CmdInfo<'a, H> {
+ pub fn new(
+ handler: H,
+ required: Vec<&'a str>,
+ optional: Vec<&'a str>,
+ unlimited: bool,
+ ) -> Self {
+ Self {
+ handler,
+ required,
+ optional,
+ unlimited,
+ }
+ }
+}
diff --git a/tap/src/tap/engine/dispatching.rs b/tap/src/tap/engine/dispatching.rs
new file mode 100644
index 0000000..21160d4
--- /dev/null
+++ b/tap/src/tap/engine/dispatching.rs
@@ -0,0 +1,45 @@
+use crate::engine::structs::{Engine, CmdInfo};
+
+
+fn validate_args(info: &CmdInfo<'_, H>, args: &[&str]) -> Option {
+ let ln = args.len();
+ let r = &info.required;
+ let o = &info.optional;
+ let u = info.unlimited;
+ if (ln > r.len() + o.len() && !u) || ln < r.len() {
+ return Some(format!(
+ "Invalid argument length: {} (must be: [{}])",
+ ln, r.join(", ")
+ ));
+ }
+ None
+}
+
+
+impl Engine<'_> {
+ pub fn process(&mut self, cmd: &str, args: &[&str]) -> Option {
+ let err: Option;
+ if let Some(info) = self.cmds.get(cmd) {
+ if let Some(err) = validate_args(info, args) {
+ return Some(err);
+ }
+ err = (info.handler)(self, args);
+ } else if let Some(info) = self.subcmds.get(cmd) {
+ if let Some(err) = validate_args(info, args) {
+ return Some(err);
+ }
+ err = (info.handler)(args);
+ } else if let Some(info) = &self.basic_handler {
+ if let Some(err) = validate_args(info, args) {
+ return Some(err);
+ }
+ err = (info.handler)(self, args);
+ } else {
+ err = Some("Invalid command: not found".to_string());
+ }
+ match err {
+ Some(s) => return Some(format!("Process Error: {}", s)),
+ None => return None,
+ }
+ }
+}
\ No newline at end of file
diff --git a/tap/src/tap/engine/engine.rs b/tap/src/tap/engine/engine.rs
new file mode 100644
index 0000000..5bbbd3a
--- /dev/null
+++ b/tap/src/tap/engine/engine.rs
@@ -0,0 +1,37 @@
+use crate::argsparsing::ArgsP;
+use crate::engine::structs::{Engine, CmdInfoSubcmd, CmdInfoCmd};
+use std::collections::HashMap;
+
+
+impl<'a> Engine<'a> {
+ pub fn new(args_p: &'a mut ArgsP) -> Self {
+ Self {
+ args_p,
+ cmds: HashMap::new(),
+ subcmds: HashMap::new(),
+ basic_handler: None,
+ }
+ }
+
+ pub fn new_command(&mut self, name: &'a str, cmdinfo: CmdInfoCmd<'a>) {
+ self.cmds.insert(name, cmdinfo);
+ }
+
+ pub fn new_subcommand(&mut self, name: &'a str, cmdinfo: CmdInfoSubcmd<'a>) {
+ self.subcmds.insert(name, cmdinfo);
+ }
+
+ pub fn work(&mut self, args: &[&str]) -> Result<(), String> {
+ self.args_p.parse_ags_to_self(args);
+ if self.args_p.args.is_empty() {
+ return Err("No command provided".to_string());
+ }
+ let all_args = self.args_p.args.clone();
+ let cmd = all_args[0].as_str();
+ let args_refs: Vec<&str> = all_args[1..].iter().map(|s| s.as_str()).collect();
+ match self.process(cmd, &args_refs) {
+ Some(val) => Err(val),
+ None => Ok(()),
+ }
+ }
+}
\ No newline at end of file
diff --git a/tap/src/tap/engine/mod.rs b/tap/src/tap/engine/mod.rs
new file mode 100644
index 0000000..a4d9e29
--- /dev/null
+++ b/tap/src/tap/engine/mod.rs
@@ -0,0 +1,4 @@
+pub mod cmdinfo;
+pub mod engine;
+pub mod structs;
+pub mod dispatching;
\ No newline at end of file
diff --git a/tap/src/tap/engine/structs.rs b/tap/src/tap/engine/structs.rs
new file mode 100644
index 0000000..46c3cf7
--- /dev/null
+++ b/tap/src/tap/engine/structs.rs
@@ -0,0 +1,22 @@
+use crate::argsparsing::ArgsP;
+use std::collections::HashMap;
+
+pub type HandlerType = fn(&mut Engine<'_>, &[&str]) -> Option;
+pub type SubHandlerType = fn(&[&str]) -> Option;
+
+pub type CmdInfoCmd<'a> = CmdInfo<'a, HandlerType>;
+pub type CmdInfoSubcmd<'a> = CmdInfo<'a, SubHandlerType>;
+
+pub struct CmdInfo<'a, H> {
+ pub handler: H,
+ pub required: Vec<&'a str>,
+ pub optional: Vec<&'a str>,
+ pub unlimited: bool,
+}
+
+pub struct Engine<'a> {
+ pub args_p: &'a mut ArgsP,
+ pub cmds: HashMap<&'a str, CmdInfoCmd<'a>>,
+ pub subcmds: HashMap<&'a str, CmdInfoSubcmd<'a>>,
+ pub basic_handler: Option>,
+}
\ No newline at end of file
diff --git a/tap/src/tap/mod.rs b/tap/src/tap/mod.rs
new file mode 100644
index 0000000..cf895d7
--- /dev/null
+++ b/tap/src/tap/mod.rs
@@ -0,0 +1,3 @@
+pub mod engine;
+pub mod argsparsing;
+pub mod color;
\ No newline at end of file
diff --git a/utils/dialogue.go b/utils/dialogue.go
deleted file mode 100644
index 643e4c7..0000000
--- a/utils/dialogue.go
+++ /dev/null
@@ -1,112 +0,0 @@
-package utils
-
-import (
- "fmt"
- "slices"
-
- "github.com/eiannone/keyboard"
- "github.com/pt-main/tap/color"
-)
-
-// DialogueType represents the mode of user interaction.
-type DialogueType int
-
-const (
- // ArrowsDialogueType enables keyboard arrow navigation.
- ArrowsDialogueType DialogueType = iota
- // InputDialogueType reads a typed string from stdin.
- InputDialogueType
-)
-
-// DialogueFormat holds the prompt text and interaction mode for a dialogue.
-type DialogueFormat struct {
- Text string
- Type DialogueType
-}
-
-// Run executes the dialogue and returns the selected variant.
-func (df *DialogueFormat) Run(variants []string) (string, error) {
- print(color.Set(df.Text))
- if df.Type == InputDialogueType {
- return df.GetVariantByInput(variants)
- } else if df.Type == ArrowsDialogueType {
- return df.GetVariantByArrows(variants)
- } else {
- return "", fmt.Errorf("Invalid dialogue type.")
- }
-}
-
-// GetVariantByInput reads a line from stdin and validates it against allowed variants.
-func (df *DialogueFormat) GetVariantByInput(variants []string) (string, error) {
- var variant string
- fmt.Scan(&variant)
- if !slices.Contains(variants, variant) {
- return "", fmt.Errorf("Invalid input: unknown variant")
- }
- return variant, nil
-}
-
-// GetVariantByArrows displays an arrow-navigable menu and returns the chosen item.
-func (df *DialogueFormat) GetVariantByArrows(variants []string) (string, error) {
- if err := keyboard.Open(); err != nil {
- return "", err
- }
- defer keyboard.Close()
-
- var variant int
- variantsLength := len(variants)
-
- form := func() string {
- result := ""
- for idx, val := range variants {
- if idx == variant {
- result += "[?RD]* " + val + "[?RT]\n"
- } else {
- result += "- " + val + "\n"
- }
- }
- return color.Set(result)
- }
-
- rw := NewRewriter()
- rw.Write(form())
-
- for {
- _, key, err := keyboard.GetKey()
- if err != nil {
- return "", err
- }
-
- switch key {
- case keyboard.KeyArrowUp:
- if variant != 0 {
- variant -= 1
- }
- rw.Write(form())
- case keyboard.KeyArrowDown:
- if variant != (variantsLength - 1) {
- variant += 1
- }
- rw.Write(form())
- case keyboard.KeyEsc:
- fmt.Println("Exiting...")
- return "", err
- case keyboard.KeyEnter:
- return variants[variant], nil
- }
- }
-}
-
-// RunCheckVariant runs the dialogue and reports whether the result matches the expected variant.
-func (df *DialogueFormat) RunCheckVariant(variants []string, variant string) (bool, error) {
- v, err := df.Run(variants)
- return v == variant, err
-}
-
-// NewDialogue creates a DialogueFormat with the given type and prompt text.
-func NewDialogue(dtype DialogueType, form string) *DialogueFormat {
- return &DialogueFormat{
- Type: dtype,
- Text: form,
- }
-}
diff --git a/utils/rewriter.go b/utils/rewriter.go
deleted file mode 100644
index 1c97fb7..0000000
--- a/utils/rewriter.go
+++ /dev/null
@@ -1,46 +0,0 @@
-package utils
-
-import (
- "fmt"
- "strings"
-)
-
-// Rewriter provides in-place terminal output rewriting using ANSI escape codes.
-type Rewriter struct {
- lastLines int
-}
-
-func NewRewriter() *Rewriter {
- return &Rewriter{}
-}
-
-// Write replaces the previously written text with new content without scrolling.
-func (r *Rewriter) Write(text string) {
- text = strings.TrimSpace(text)
- lines := strings.Split(text, "\n")
- if len(lines) > 0 && lines[len(lines)-1] == "" {
- lines = lines[:len(lines)-1]
- }
- currentLines := len(lines) - 1
- if r.lastLines > 0 {
- fmt.Printf("\033[%dA", r.lastLines)
- }
- for i, line := range lines {
- fmt.Print("\033[2K\r")
- if i < len(lines)-1 {
- fmt.Println(line)
- } else {
- fmt.Print(line)
- }
- }
- if currentLines < r.lastLines {
- extra := r.lastLines - currentLines
- for i := 0; i < extra; i++ {
- fmt.Print("\033[1B")
- fmt.Print("\033[2K\r")
- }
-
- fmt.Printf("\033[%dA", extra)
- }
- r.lastLines = currentLines
-}