Initial QSfera import

This commit is contained in:
Курнат Андрей
2026-06-07 10:20:04 +03:00
commit 2315f25754
16485 changed files with 4826827 additions and 0 deletions
+67
View File
@@ -0,0 +1,67 @@
package cmdline
import (
"strings"
"github.com/gookit/goutil/internal/comfunc"
"github.com/gookit/goutil/strutil"
)
// LineBuilder build command line string.
// codes refer from strings.Builder
type LineBuilder struct {
strings.Builder
}
// NewBuilder create
func NewBuilder(binFile string, args ...string) *LineBuilder {
b := &LineBuilder{}
if binFile != "" {
b.AddArg(binFile)
}
b.AddArray(args)
return b
}
// ResetGet value, will reset after get.
func (b *LineBuilder) ResetGet() string {
defer b.Reset()
return b.String()
}
// AddArg to builder
func (b *LineBuilder) AddArg(arg string) {
_, _ = b.WriteString(arg)
}
// AddArgs to builder
func (b *LineBuilder) AddArgs(args ...string) {
b.AddArray(args)
}
// AddArray to builder
func (b *LineBuilder) AddArray(args []string) {
for _, arg := range args {
_, _ = b.WriteString(arg)
}
}
// AddAny args to builder
func (b *LineBuilder) AddAny(args ...any) {
for _, arg := range args {
_, _ = b.WriteString(strutil.SafeString(arg))
}
}
// WriteString arg string to the builder, will auto quote special string.
// refer strconv.Quote()
func (b *LineBuilder) WriteString(a string) (int, error) {
// add sep on not-first write.
if b.Len() != 0 {
_ = b.WriteByte(' ')
}
return b.Builder.WriteString(comfunc.ShellQuote(a))
}
+15
View File
@@ -0,0 +1,15 @@
// Package cmdline provide quick build and parse cmd line string.
package cmdline
import "github.com/gookit/goutil/internal/comfunc"
// LineBuild build command line string by given args.
func LineBuild(binFile string, args []string) string {
return NewBuilder(binFile, args...).String()
}
// ParseLine input command line text. alias of the StringToOSArgs()
func ParseLine(line string) []string { return NewParser(line).Parse() }
// Quote string in shell command env
func Quote(s string) string { return comfunc.ShellQuote(s) }
+174
View File
@@ -0,0 +1,174 @@
package cmdline
import (
"bytes"
"os/exec"
"strings"
"github.com/gookit/goutil/comdef"
"github.com/gookit/goutil/internal/varexpr"
"github.com/gookit/goutil/strutil"
)
// LineParser struct
// parse input command line to []string, such as cli os.Args
type LineParser struct {
parsed bool
// Line the full input command line text
// eg `kite top sub -a "this is a message" --foo val1 --bar "val 2"`
Line string
// ParseEnv parse ENV var on the line.
ParseEnv bool
// the exploded nodes by space.
nodes []string
// the parsed args
args []string
// temp value
quoteChar byte
quoteIndex int // if > 0, mark is not on start
tempNode bytes.Buffer
}
// NewParser create
func NewParser(line string) *LineParser {
return &LineParser{Line: line}
}
// WithParseEnv with parse ENV var
func (p *LineParser) WithParseEnv() *LineParser {
p.ParseEnv = true
return p
}
// AlsoEnvParse input command line text to os.Args, will parse ENV var
func (p *LineParser) AlsoEnvParse() []string {
p.ParseEnv = true
return p.Parse()
}
// NewExecCmd quick create exec.Cmd by cmdline string
func (p *LineParser) NewExecCmd() *exec.Cmd {
// parse get bin and args
binName, args := p.BinAndArgs()
// create a new Cmd instance
return exec.Command(binName, args...)
}
// BinAndArgs get binName and args
func (p *LineParser) BinAndArgs() (bin string, args []string) {
p.Parse() // ensure parsed.
ln := len(p.args)
if ln == 0 {
return
}
bin = p.args[0]
if ln > 1 {
args = p.args[1:]
}
return
}
// Parse input command line text to os.Args
func (p *LineParser) Parse() []string {
if p.parsed {
return p.args
}
p.parsed = true
p.Line = strings.TrimSpace(p.Line)
if p.Line == "" {
return p.args
}
// enable parse Env var
if p.ParseEnv {
p.Line = varexpr.SafeParse(p.Line)
}
p.nodes = strings.Split(p.Line, " ")
if len(p.nodes) == 1 {
p.args = p.nodes
return p.args
}
for i := 0; i < len(p.nodes); i++ {
node := p.nodes[i]
if node == "" {
continue
}
p.parseNode(node)
}
p.nodes = p.nodes[:0]
if p.tempNode.Len() > 0 {
p.appendTempNode()
}
return p.args
}
func (p *LineParser) parseNode(node string) {
maxIdx := len(node) - 1
start, end := node[0], node[maxIdx]
// in quotes
if p.quoteChar != 0 {
p.tempNode.WriteByte(' ')
// end quotes
if end == p.quoteChar {
if p.quoteIndex > 0 {
p.tempNode.WriteString(node) // eg: node="--pretty=format:'one two'"
} else {
p.tempNode.WriteString(node[:maxIdx]) // remove last quote
}
p.appendTempNode()
} else { // goon ... write to temp node
p.tempNode.WriteString(node)
}
return
}
// quote start
if start == comdef.DoubleQuote || start == comdef.SingleQuote {
// only one words. eg: `-m "msg"`
if end == start {
p.args = append(p.args, node[1:maxIdx])
return
}
p.quoteChar = start
p.tempNode.WriteString(node[1:])
} else if end == comdef.DoubleQuote || end == comdef.SingleQuote {
p.args = append(p.args, node) // only one node: `msg"`
} else {
// eg: --pretty=format:'one two three'
if strutil.ContainsByte(node, comdef.DoubleQuote) {
p.quoteIndex = 1 // mark is not on start
p.quoteChar = comdef.DoubleQuote
} else if strutil.ContainsByte(node, comdef.SingleQuote) {
p.quoteIndex = 1
p.quoteChar = comdef.SingleQuote
}
// in quote, append to temp-node
if p.quoteChar != 0 {
p.tempNode.WriteString(node)
} else {
p.args = append(p.args, node)
}
}
}
func (p *LineParser) appendTempNode() {
p.args = append(p.args, p.tempNode.String())
// reset context value
p.quoteChar = 0
p.quoteIndex = 0
p.tempNode.Reset()
}