Compare commits

...

21 Commits

Author SHA1 Message Date
Jeff Carr 4efd88947c good stuff 2025-09-05 01:52:54 -05:00
Jeff Carr e621f228ba linux only code 2025-02-22 06:53:24 -06:00
Jeff Carr 590ce71782 new autogenpb 2025-02-20 09:39:36 -06:00
Jeff Carr 2b1f7f54d5 testing for using 'sudo' 2025-02-14 18:40:31 -06:00
Jeff Carr d70cf82ecc sudo with pseudo tty 2025-02-12 15:11:29 -06:00
Jeff Carr 364d666eb6 add RunVerbose() RunVerboseOnError() 2025-02-07 11:22:11 -06:00
Jeff Carr 0146183226 remote output 2025-01-29 12:25:44 -06:00
Jeff Carr 098c7d3771 forge config has user xterm settings 2025-01-16 10:25:29 -06:00
Jeff Carr 1534b9fbbc shell exec for 'forge commit' 2025-01-12 06:35:36 -06:00
Jeff Carr d5f45aef13 does this exit correctly? is it done or not done? 2024-12-18 23:06:00 -06:00
Jeff Carr 3408d9434b purge old code 2024-12-18 17:59:01 -06:00
Jeff Carr d564d4f2d7 make one you can send a 'nil' to it 2024-12-16 23:59:16 -06:00
Jeff Carr 2b1b847143 output is too fast to capture with RunRealtime() 2024-12-15 08:47:24 -06:00
Jeff Carr f39c2a86a6 still need to debug what is being left behind 2024-12-11 13:54:17 -06:00
Jeff Carr 8d692299b0 RunEcho() 2024-12-10 01:48:02 -06:00
Jeff Carr 99eded5398 wrong return var 2024-12-07 16:49:40 -06:00
Jeff Carr 62e2d64ff9 quiet shell output 2024-12-06 01:49:28 -06:00
Jeff Carr 3ec8400a9d add RunQuiet() 2024-12-01 22:22:27 -06:00
Jeff Carr f0b4869dfa RunRealtime() 2024-11-22 21:22:24 -06:00
Jeff Carr 7afc01d14e try a really fast loop to see how it works out 2024-11-17 16:08:00 -06:00
Jeff Carr a2532b2f3b more realtime-ish output when humans are watching 2024-11-15 09:13:06 -06:00
10 changed files with 314 additions and 130 deletions

View File

@ -1,5 +1,9 @@
all:
GO111MODULE=off go build
all: goimports vet
vet:
@GO111MODULE=off go vet
@echo this go library package builds okay
goimports:
goimports -w *.go

137
cmd.go
View File

@ -3,6 +3,7 @@ package shell
import (
"errors"
"fmt"
"os"
"time"
"github.com/go-cmd/cmd"
@ -43,7 +44,7 @@ func Run(argv []string) cmd.Status {
// where the have rocked out a proper smart read on both filehandles
// https://dave.cheney.net/2013/04/30/curious-channels
func PathRun(path string, argv []string) cmd.Status {
return PathRunLog(path, argv, NOW)
return PathRunLog(path, argv, INFO)
}
// the actual wrapper around go-cmd/cmd
@ -122,14 +123,44 @@ func PathRunLog(path string, argv []string, logf *log.LogFlag) cmd.Status {
return s
}
// uses the 'log' package to disable echo to STDOUT
// only echos if you enable the shell.INFO log flag
func RunQuiet(args []string) cmd.Status {
return PathRunLog("", args, INFO)
}
// uses the 'log' package to disable echo to STDOUT
// only echos if you enable the shell.INFO log flag
func PathRunQuiet(pwd string, args []string) cmd.Status {
return PathRunLog(pwd, args, INFO)
}
// absolutely doesn't echo anything
// bad for now. leaves goroutines running all over the place
func PathRunQuietBad(pwd string, args []string) cmd.Status {
// send blank path == use current golang working directory
func RunRealtime(args []string) cmd.Status {
return PathRunRealtime("", args)
}
func RunEcho(cmd []string) cmd.Status {
result := RunQuiet(cmd)
pwd, _ := os.Getwd()
log.Warn("shell.RunEcho() cmd:", cmd, pwd)
log.Warn("shell.RunEcho() Exit:", result.Exit)
log.Warn("shell.RunEcho() Error:", result.Error)
for _, line := range result.Stdout {
log.Warn("STDOUT:", line)
}
for _, line := range result.Stderr {
log.Warn("STDERR:", line)
}
return result
}
// echos twice a second if anything sends to STDOUT or STDERR
// not great, but it's really just for watching things run in real time anyway
// TODO: fix \r handling for things like git-clone so the terminal doesn't
// have to do a \n newline each time.
// TODO: add timeouts and status of things hanging around forever
func PathRunRealtime(pwd string, args []string) cmd.Status {
// Check if the slice has at least one element (the command name)
if len(args) == 0 {
var s cmd.Status
@ -145,17 +176,34 @@ func PathRunQuietBad(pwd string, args []string) cmd.Status {
}
statusChan := findCmd.Start() // non-blocking
ticker := time.NewTicker(2 * time.Second)
ticker := time.NewTicker(100 * time.Microsecond)
// this is interesting, maybe useful, but wierd, but neat. interesting even
// Print last line of stdout every 2s
go func() {
// loop very quickly, but only print the line if it changes
var lastout string
var lasterr string
for range ticker.C {
status := findCmd.Status()
n := len(status.Stdout)
if n != 0 {
fmt.Println("todo:removethisecho", status.Stdout[n-1])
fmt.Println("status", status.Exit)
newline := status.Stdout[n-1]
if lastout != newline {
lastout = newline
log.Info(lastout)
}
}
n = len(status.Stderr)
if n != 0 {
newline := status.Stderr[n-1]
if lasterr != newline {
lasterr = newline
log.Info(lasterr)
}
}
if status.Complete {
return
}
}
}()
@ -177,29 +225,14 @@ func PathRunQuietBad(pwd string, args []string) cmd.Status {
}
// Block waiting for command to exit, be stopped, or be killed
// there are things being left around here. debug this
finalStatus := <-statusChan
return finalStatus
}
func blah(cmd []string) {
r := Run(cmd)
log.Info("cmd =", r.Cmd)
log.Info("complete =", r.Complete)
log.Info("exit =", r.Exit)
log.Info("err =", r.Error)
log.Info("len(stdout+stderr) =", len(r.Stdout))
}
// run these to see confirm the sytem behaves as expected
func RunTest() {
blah([]string{"ping", "-c", "3", "localhost"})
blah([]string{"exit", "0"})
blah([]string{"exit", "-1"})
blah([]string{"true"})
blah([]string{"false"})
blah([]string{"grep", "root", "/etc/", "/proc/cmdline", "/usr/bin/chmod"})
blah([]string{"grep", "root", "/proc/cmdline"})
fmt.Sprint("blahdone")
if len(finalStatus.Cmd) != 0 {
if string(finalStatus.Cmd) != "go" {
log.Info("shell.Run() ok goroutine end?", finalStatus.Cmd, finalStatus.Exit)
}
}
return findCmd.Status()
}
// this is stuff from a long time ago that there must be a replacement for
@ -209,3 +242,49 @@ func RemoveFirstElement(slice []string) (string, []string) {
}
return slice[0], slice[1:] // Return the slice without the first element
}
func RunVerbose(cmd []string) (*cmd.Status, error) {
pwd, _ := os.Getwd()
log.Info("Running:", pwd, cmd)
r, err := RunStrict(cmd)
if err != nil {
log.Info("Error", cmd, err)
}
for _, line := range r.Stdout {
log.Info(line)
}
for _, line := range r.Stderr {
log.Info(line)
}
return r, err
}
func RunVerboseOnError(cmd []string) (*cmd.Status, error) {
r, err := RunStrict(cmd)
if err == nil {
return r, err
}
pwd, _ := os.Getwd()
log.Info("Run Error:", pwd, cmd, err)
for _, line := range r.Stdout {
log.Info(line)
}
for _, line := range r.Stderr {
log.Info(line)
}
return r, err
}
func RunStrict(cmd []string) (*cmd.Status, error) {
pwd, _ := os.Getwd()
result := PathRunQuiet(pwd, cmd)
if result.Error != nil {
log.Warn(pwd, cmd, "wow. golang is cool. an os.Error:", result.Error)
return &result, result.Error
}
if result.Exit != 0 {
// log.Warn(cmd, "failed with", result.Exit, repo.GetGoPath())
return &result, errors.New(fmt.Sprint(cmd, "failed with", result.Exit))
}
return &result, nil
}

71
exec.go Normal file
View File

@ -0,0 +1,71 @@
package shell
import (
"errors"
"os"
"os/exec"
"go.wit.com/log"
)
func Exec(args []string) error {
if len(args) == 0 {
return errors.New("Error: Command slice is empty.")
}
// Start a long-running process, capture stdout and stderr
a, b := RemoveFirstElement(args)
process := exec.Command(a, b...)
process.Stderr = os.Stderr
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Start()
err := process.Wait()
log.Log(INFO, "shell.Exec() err =", err)
return nil
}
func ExecCheck(args []string) error {
if len(args) == 0 {
return errors.New("Error: Command slice is empty.")
}
// Start a long-running process, capture stdout and stderr
a, b := RemoveFirstElement(args)
process := exec.Command(a, b...)
process.Stderr = os.Stderr
process.Stdin = os.Stdin
process.Stdout = os.Stdout
err := process.Run()
if err != nil {
log.Info("ExecCheck() err", err)
return err
}
// log.Info("ExecCheck() nil")
return nil
}
func PathExecVerbose(path string, args []string) error {
if len(args) == 0 {
return errors.New("Error: Command slice is empty.")
}
// Start a long-running process, capture stdout and stderr
a, b := RemoveFirstElement(args)
process := exec.Command(a, b...)
process.Dir = path
process.Stderr = os.Stderr
process.Stdin = os.Stdin
process.Stdout = os.Stdout
err := process.Run()
log.Info("Exec() cmd:", args)
if err != nil {
log.Info("ExecCheck() err", err)
return err
}
// log.Info("ExecCheck() nil")
return nil
}

62
exec_linux.go Normal file
View File

@ -0,0 +1,62 @@
package shell
import (
"fmt"
"os"
"os/exec"
"syscall"
)
func SudoRaw(c []string) {
args := []string{"-S"}
args = append(args, c...)
cmd := exec.Command("sudo", args...)
// Assign the current process's standard input, output, and error
cmd.Stderr = os.Stderr
cmd.Stdout = os.Stdout
cmd.Stdin = os.Stdin
// Ensure the process has a terminal session
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true, // Start a new session
}
err := cmd.Run()
if err != nil {
fmt.Println("Command execution failed:", err)
}
}
func Sudo(c []string) error {
args := []string{"-S"}
// args := []string{}
args = append(args, c...)
cmd := exec.Command("sudo", args...)
// Open the terminal device directly to preserve input/output control
tty, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
if err != nil {
fmt.Println("Failed to open /dev/tty:", err)
return err
}
defer tty.Close()
// Assign the TTY explicitly
cmd.Stdin = tty
cmd.Stdout = tty
cmd.Stderr = tty
// Ensure the new process gets its own session
cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true, // Start a new session
}
// Run the command
if err := cmd.Run(); err != nil {
fmt.Println("Command execution failed:", err)
}
fmt.Println("\nProcess finished. TTY restored.")
return nil
}

View File

@ -3,8 +3,6 @@ package shell
// old code and probably junk
import (
"io/ioutil"
"net/http"
"os"
"os/exec"
@ -16,11 +14,11 @@ import (
var callback func(interface{}, int)
var shellStdout *os.File
var shellStderr *os.File
// var shellStdout *os.File
// var shellStderr *os.File
var spewOn bool = false
var quiet bool = false
// var spewOn bool = false
// var quiet bool = false
// var msecDelay int = 20 // number of milliseconds to delay between reads with no data
@ -42,26 +40,6 @@ func InitCallback(f func(interface{}, int)) {
callback = f
}
// this means it won't copy all the output to STDOUT
func Quiet(q bool) {
quiet = q
}
/*
func Script(cmds string) int {
// split on new lines (while we are at it, handle stupid windows text files
lines := strings.Split(strings.Replace(cmds, "\r\n", "\n", -1), "\n")
for _, line := range lines {
line = Chomp(line) // this is like 'chomp' in perl
log.Log(INFO, "LINE:", line)
time.Sleep(1)
RunString(line)
}
return 0
}
*/
func Unlink(filename string) bool {
if err := os.Remove(filename); err != nil {
return Exists(filename)
@ -107,66 +85,11 @@ func Exists(filename string) bool {
return true
}
// makes the directory
func Mkdir(dir string) bool {
if Dir(dir) {
// already a dir
return true
}
if Exists(dir) {
// something else is there
return false
}
Run([]string{"mkdir", "-p", dir})
return true
}
func IsDir(dirname string) bool {
return Dir(dirname)
}
// return true if the filename exists (cross-platform)
func Dir(dirname string) bool {
func IsDir(dirname string) bool {
info, err := os.Stat(Path(dirname))
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}
// Cat a file into a string
func Cat(filename string) string {
buffer, err := ioutil.ReadFile(Path(filename))
// log.Log(INFO, "buffer =", string(buffer))
if err != nil {
return ""
}
return string(buffer)
}
func RunPathHttpOut(path string, cmd []string, w http.ResponseWriter, r *http.Request) error {
log.Warn("Run(): ", cmd)
process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
process.Dir = path
process.Stderr = os.Stderr
process.Stdin = r.Body
process.Stdout = w
process.Start()
err := process.Wait()
log.Warn("shell.Exec() err =", err)
return err
}
func RunHttpOut(cmd []string, w http.ResponseWriter, r *http.Request) error {
log.Warn("NewRun() ", cmd)
process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
process.Stderr = os.Stderr
process.Stdin = r.Body
process.Stdout = w
process.Start()
err := process.Wait()
log.Warn("shell.Exec() err =", err)
return err
}

17
splitNewLines.go Normal file
View File

@ -0,0 +1,17 @@
package shell
import "regexp"
// splits strings. should work all the time
// A string with mixed line endings, including old Mac style (\r)
func SplitNewLines(input string) []string {
// This regex matches a carriage return and optional newline, OR just a newline.
// This covers \r\n, \n, and \r.
re := regexp.MustCompile(`\r\n?|\n|\r`)
// The -1 means there is no limit to the number of splits.
lines := re.Split(input, -1)
// Output: ["line one" "line two" "line three" "line four"]
return lines
}

View File

@ -1,12 +1,6 @@
package shell
import (
"bufio"
"bytes"
"io"
"os/exec"
)
/*
var FileMap map[string]*File
var readBufferSize int
@ -54,3 +48,4 @@ func New() *OldShell {
return &tmp
}
*/

23
time.go
View File

@ -51,6 +51,14 @@ func GetDurationStamp(t time.Time) string {
return FormatDuration(duration)
}
// allows nil
func HumanDuration(d *time.Duration) string {
if d == nil {
return ""
}
return FormatDuration(*d)
}
func FormatDuration(d time.Duration) string {
result := ""
@ -94,6 +102,19 @@ func FormatDuration(d time.Duration) string {
if ms > 100 {
// todo: print .3s, etc ?
}
result += fmt.Sprintf("%dms", ms)
if ms > 0 {
result += fmt.Sprintf("%dms", ms)
return result
}
// report in milliseconds
mc := int(d.Microseconds())
if mc > 0 {
result += fmt.Sprintf("%dmc", mc)
return result
}
ns := int(d.Nanoseconds())
result += fmt.Sprintf("%dns", ns)
return result
}

View File

@ -34,7 +34,7 @@ func Wget(url string) *bytes.Buffer {
log.Log(INFO, "res.StatusCode: %d\n", resp.StatusCode)
if resp.StatusCode != 200 {
handleError(fmt.Errorf(fmt.Sprint("%d", resp.StatusCode)), -1)
handleError(fmt.Errorf("%d", resp.StatusCode), -1)
return nil
}

View File

@ -3,11 +3,25 @@ package shell
import (
"fmt"
"os"
"strings"
"go.wit.com/log"
)
// not sure why I wrote this or what it is for
// this is because I'm crazy. how crazy you ask? fucking crazy!
// hehe. If you haven't ever written code that you look back
// at like this and go 'what the fuck is this shit', then
// realize that you wrote it, then go 'what the fuck is this shit'
// and then still look at it and wonder, "how early was this. how much did I not know when I wrote this"
// then, if you haven't done those kinds of things, then don't
// ever fucking come up to me and tell me that I'm nuts
// because, you are not as good as me then. It's very complicated
// to work on very complicated things. I don't care how smart you are,
// you can totally forget about shit you wrote and then have to come back to it later
// also, what the fuck was I thinking with the function 'scanToParent()' as a function that takes
// a pid arg? what? is this like pstree? I'm not sure what I wanted this for
// but it sounds interesting.
func scanToParent(pid int) (bool, string) {
ppid, err := GetPPID(pid)
if err != nil {
@ -35,8 +49,6 @@ func scanToParent(pid int) (bool, string) {
case "make":
// keep digging for the parent xterm
return scanToParent(ppid)
default:
return false, comm
}
if comm == "bash" {
}
@ -81,8 +93,6 @@ func Shell() string {
switch envsh {
case "/bin/bash":
return "bash"
default:
return envsh
}
return envsh
}
@ -116,16 +126,17 @@ func XtermCmd(path string, cmd []string) {
// runs an xterm
// waits until xterm exits
func XtermCmdWait(path string, cmd []string) {
var argsXterm = getXtermCmd(cmd)
// var argsXterm = getXtermCmd(cmd)
log.Info("XtermCmd() path =", path, "cmd =", argsXterm)
log.Info("XtermCmd() path =", path, "cmd =", cmd)
// keeps git diff from exiting on small diffs
os.Setenv("LESS", "-+F -+X -R")
PathRunLog(path, argsXterm, INFO)
PathRunLog(path, cmd, INFO)
}
/*
// spawns an xterm with something you can run at a command line
// then executes bash
func XtermCmdBash(path string, cmd []string) {
@ -138,3 +149,4 @@ func XtermCmdBash(path string, cmd []string) {
log.Info("XtermCmd() path =", path, "cmd =", tmp)
go PathRunLog(path, tmp, INFO)
}
*/