Compare commits

..

32 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
Jeff Carr bc31c85413 rename args argv 2024-11-13 11:56:25 -06:00
Jeff Carr d986f8f331 go-cmd output logging option
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-13 10:33:54 -06:00
Jeff Carr d887da32f2 use go-cmd/cmd and purge old code 2024-11-08 06:43:04 -06:00
Jeff Carr ae5cadf545 attempt stdout to http
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-04 02:15:48 -06:00
Jeff Carr f0b70125e8 better notes
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 21:40:30 -05:00
Jeff Carr e2c6ba0c2b commit notes
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 21:39:25 -05:00
Jeff Carr 2940358bf9 crapnuts
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-11-01 04:38:30 -05:00
Jeff Carr 73cfe9b760 notes on format duration
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-10-12 22:51:30 -05:00
Jeff Carr a3c2627caf return the output 2024-04-15 05:07:06 -05:00
Jeff Carr 5553bda497 add shell.Output() 2024-03-21 19:44:38 -05:00
Jeff Carr 16715e0252 make an exec to STDOUT
Signed-off-by: Jeff Carr <jcarr@wit.com>
2024-03-21 16:17:31 -05:00
14 changed files with 686 additions and 534 deletions

View File

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

View File

@ -1,98 +0,0 @@
package shell
/*
perl 'chomp'
send it anything, always get back a string
*/
import (
"bytes"
"fmt"
"reflect"
"strings"
"go.wit.com/log"
)
// import "github.com/davecgh/go-spew/spew"
func chompBytesBuffer(buf *bytes.Buffer) string {
var bytesSplice []byte
bytesSplice = buf.Bytes()
return Chomp(string(bytesSplice))
}
// TODO: obviously this is stupidly wrong
// TODO: fix this to trim fucking everything
// really world? 8 fucking years of this language
// and I'm fucking writing this? jesus. how the
// hell is everyone else doing this? Why isn't
// this already in the strings package?
func perlChomp(s string) string {
// lots of stuff in go moves around the whole block of whatever it is so lots of things are padded with NULL values
s = strings.Trim(s, "\x00") // removes NULL (needed!)
// TODO: christ. make some fucking regex that takes out every NULL, \t, ' ", \n, and \r
s = strings.Trim(s, "\n")
s = strings.Trim(s, "\n")
s = strings.TrimSuffix(s, "\r")
s = strings.TrimSuffix(s, "\n")
s = strings.TrimSpace(s) // this is like 'chomp' in perl
s = strings.TrimSuffix(s, "\n") // this is like 'chomp' in perl
return s
}
// TODO: fix this to chomp \n \r NULL \t and ' '
func Chomp(a interface{}) string {
// switch reflect.TypeOf(a) {
switch t := a.(type) {
case string:
var s string
s = a.(string)
return perlChomp(s)
case []uint8:
// log.Printf("shell.Chomp() FOUND []uint8")
var tmp []uint8
tmp = a.([]uint8)
s := string(tmp)
return perlChomp(s)
case uint64:
// log.Printf("shell.Chomp() FOUND []uint64")
s := fmt.Sprintf("%d", a.(uint64))
return perlChomp(s)
case int64:
// log.Printf("shell.Chomp() FOUND []int64")
s := fmt.Sprintf("%d", a.(int64))
return perlChomp(s)
case *bytes.Buffer:
// log.Printf("shell.Chomp() FOUND *bytes.Buffer")
var tmp *bytes.Buffer
tmp = a.(*bytes.Buffer)
if tmp == nil {
return ""
}
var bytesSplice []byte
bytesSplice = tmp.Bytes()
return Chomp(string(bytesSplice))
default:
tmp := fmt.Sprint("shell.Chomp() NO HANDLER FOR TYPE: %T", a)
handleError(fmt.Errorf(tmp), -1)
log.Warn("shell.Chomp() NEED TO MAKE CONVERTER FOR type =", reflect.TypeOf(t))
}
tmp := "shell.Chomp() THIS SHOULD NEVER HAPPEN"
handleError(fmt.Errorf(tmp), -1)
return ""
}
// this is stuff from a long time ago that there must be a replacement for
func RemoveFirstElement(slice []string) (string, []string) {
if len(slice) == 0 {
return "", slice // Return the original slice if it's empty
}
return slice[0], slice[1:] // Return the slice without the first element
}

290
cmd.go Normal file
View File

@ -0,0 +1,290 @@
package shell
import (
"errors"
"fmt"
"os"
"time"
"github.com/go-cmd/cmd"
"go.wit.com/log"
)
// this is a simplified interaction with the excellent
// go-cmd/cmd package to work 'shell' like.
// in all cases here, STDERR -> STDOUT
// If you want the output from whatever you run
// to be captured like it appears when you see it
// on the command line, this is what this tries to do
/*
if r := shell.Run([]{"ping", "-c", "3", "localhost"}); r.Error == nil {
if r.Exit == 0 {
log.Println("ran ok")
} else {
log.Println("ran")
}
// all stdout/stderr captured in r.Stdout
}
*/
// shortcut, sends a blank value for pwd
// which means the exec Dir is not set
// echos output (otherwise use RunQuiet)
func Run(argv []string) cmd.Status {
return PathRun("", argv)
}
// exec the cmd at a filepath. this does not change the working directory
// sets the exec dir if it's not an empty string
// combines stdout and stderr
// echo's output (otherwise use PathRunQuiet()
// this is basically the exact example from the go-cmd/cmd devs
// 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, INFO)
}
// the actual wrapper around go-cmd/cmd
// adds a log Flag so that echo to stdout can be enabled/disabled
func PathRunLog(path string, argv []string, logf *log.LogFlag) cmd.Status {
var save []string // combined stdout & stderr
var arg0 string
var args []string
if logf == nil {
logf = NOW
}
log.Log(logf, "shell.PathRunLog() Path =", path, "cmd =", argv)
// Check if the slice has at least one element (the command name)
if len(argv) == 0 {
var s cmd.Status
s.Error = errors.New("Error: Command slice is empty.")
return s
}
if len(argv) == 1 {
// Pass the first element as the command, and the rest as variadic arguments
arg0 = argv[0]
} else {
arg0 = argv[0]
args = argv[1:]
}
// Disable output buffering, enable streaming
cmdOptions := cmd.Options{
Buffered: false,
Streaming: true,
}
// Create Cmd with options
envCmd := cmd.NewCmdOptions(cmdOptions, arg0, args...)
if path != "" {
// set the path for exec
envCmd.Dir = path
}
// Print STDOUT and STDERR lines streaming from Cmd
doneChan := make(chan struct{})
go func() {
defer close(doneChan)
// Done when both channels have been closed
// https://dave.cheney.net/2013/04/30/curious-channels
for envCmd.Stdout != nil || envCmd.Stderr != nil {
select {
case line, open := <-envCmd.Stdout:
if !open {
envCmd.Stdout = nil
continue
}
save = append(save, line)
log.Log(logf, line)
// fmt.Println(line)
case line, open := <-envCmd.Stderr:
if !open {
envCmd.Stderr = nil
continue
}
save = append(save, line)
log.Log(logf, line)
// fmt.Println(line)
}
}
}()
// Run and wait for Cmd to return, discard Status
<-envCmd.Start()
// Wait for goroutine to print everything
<-doneChan
s := envCmd.Status()
s.Stdout = save
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)
}
// 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
s.Error = errors.New("Error: Command slice is empty.")
return s
}
// Start a long-running process, capture stdout and stderr
a, b := RemoveFirstElement(args)
findCmd := cmd.NewCmd(a, b...)
if pwd != "" {
findCmd.Dir = pwd
}
statusChan := findCmd.Start() // non-blocking
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 {
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
}
}
}()
// Stop command after 1 hour
go func() {
<-time.After(1 * time.Hour)
findCmd.Stop()
}()
// Check if command is done
select {
case finalStatus := <-statusChan:
log.Info("finalStatus =", finalStatus.Exit, finalStatus.Error)
return finalStatus
// done
default:
// no, still running
}
// Block waiting for command to exit, be stopped, or be killed
// there are things being left around here. debug this
finalStatus := <-statusChan
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
func RemoveFirstElement(slice []string) (string, []string) {
if len(slice) == 0 {
return "", slice // Return the original slice if it's empty
}
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
}

31
int.go
View File

@ -1,31 +0,0 @@
package shell
/*
send it anything, always get back an int
*/
// import "log"
// import "reflect"
// import "strings"
// import "bytes"
import "strconv"
func Int(s string) int {
s = Chomp(s)
i, err := strconv.Atoi(s)
if err != nil {
handleError(err, -1)
return 0
}
return i
}
func Int64(s string) int64 {
s = Chomp(s)
i, err := strconv.Atoi(s)
if err != nil {
handleError(err, -1)
return 0
}
return int64(i)
}

95
posix.go Normal file
View File

@ -0,0 +1,95 @@
package shell
// old code and probably junk
import (
"os"
"os/exec"
"go.wit.com/log"
)
// TODO: look at https://github.com/go-cmd/cmd/issues/20
// use go-cmd instead here?
var callback func(interface{}, int)
// var shellStdout *os.File
// var shellStderr *os.File
// var spewOn bool = false
// var quiet bool = false
// var msecDelay int = 20 // number of milliseconds to delay between reads with no data
// var bytesBuffer bytes.Buffer
// var bytesSplice []byte
func handleError(c interface{}, ret int) {
log.Log(INFO, "shell.Run() Returned", ret)
if callback != nil {
callback(c, ret)
}
}
func init() {
callback = nil
}
func InitCallback(f func(interface{}, int)) {
callback = f
}
func Unlink(filename string) bool {
if err := os.Remove(filename); err != nil {
return Exists(filename)
} else {
return Exists(filename)
}
}
// run interactively. output from the cmd is in real time
// shows all the output. For example, 'ping -n localhost'
// shows the output like you would expect to see
func RunSimple(cmd []string) error {
log.Log(INFO, "NewRun() ", cmd)
return PathRunSimple("", cmd)
}
func PathRunSimple(workingpath string, cmd []string) error {
log.Log(INFO, "NewRun() ", cmd)
process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
// Set the working directory
process.Dir = workingpath
process.Stderr = os.Stderr
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Start()
err := process.Wait()
if err != nil {
log.Log(INFO, "shell.Exec() err =", err)
}
return err
}
// return true if the filename exists (cross-platform)
// return true if the filename exists (cross-platform)
func Exists(filename string) bool {
_, err := os.Stat(Path(filename))
if os.IsNotExist(err) {
return false
}
return true
}
// return true if the filename exists (cross-platform)
func IsDir(dirname string) bool {
info, err := os.Stat(Path(dirname))
if os.IsNotExist(err) {
return false
}
return info.IsDir()
}

213
run.go
View File

@ -1,213 +0,0 @@
package shell
import (
"errors"
"os"
"os/exec"
"strings"
"syscall"
"go.wit.com/log"
)
var msecDelay int = 20 // check every 20 milliseconds
// TODO: look at https://github.com/go-cmd/cmd/issues/20
// use go-cmd instead here?
// exiterr.Sys().(syscall.WaitStatus)
// run command and return it's output
/*
func RunCapture(cmdline string) string {
test := New()
test.Exec(cmdline)
return Chomp(test.Buffer)
}
func RunWait(args []string) *OldShell {
test := New()
cmdline := strings.Join(args, " ")
test.Exec(cmdline)
return test
}
*/
// var newfile *shell.File
func RunString(args string) bool {
// return false
parts := strings.Split(args, " ")
return Run(parts)
}
func Run(args []string) bool {
dir, err := os.Getwd()
if err != nil {
return false
}
r := RunPath(dir, args)
if r.ok {
return true
}
return false
}
var ErrorArgvEmpty error = errors.New("command was empty")
type RunResult struct {
ok bool
argv []string
path string
output []byte
err error
outerr error
}
// run, but set the working path
func RunPath(path string, args []string) *RunResult {
r := new(RunResult)
r.path = path
r.argv = args
if len(args) == 0 {
r.ok = true
r.err = ErrorArgvEmpty
return r
}
if args[0] == "" {
r.ok = false
r.err = ErrorArgvEmpty
return r
}
thing := args[0]
parts := args[1:]
cmd := exec.Command(thing, parts...)
cmd.Dir = path
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Info("path =", path, "cmd =", strings.Join(args, " "))
if err := cmd.Run(); err != nil {
// Handle error if the command execution fails
// log.Info("RunPath() failed")
// log.Info("cmd.Enviorn =", cmd.Environ())
out, outerr := cmd.Output()
// log.Info("cmd.output =", out)
// log.Info("cmd.output err=", outerr)
// log.Info("path =", path)
// log.Info("args =", args)
// log.Info("err =", err.Error())
r.ok = false
r.err = err
r.output = out
r.outerr = outerr
return r
}
out, outerr := cmd.Output()
r.output = out
r.outerr = outerr
r.ok = true
return r
}
// send the path and the command
func RunCmd(workingpath string, parts []string) (error, bool, string) {
if len(parts) == 0 {
log.Warn("command line was empty")
return errors.New("empty"), false, ""
}
if parts[0] == "" {
log.Warn("command line was empty")
return errors.New("empty"), false, ""
}
thing := parts[0]
parts = parts[1:]
log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
// Create the command
cmd := exec.Command(thing, parts...)
// Set the working directory
cmd.Dir = workingpath
// Execute the command
output, err := cmd.CombinedOutput()
if err != nil {
if thing == "git" {
log.Log(INFO, "git ERROR. maybe okay", workingpath, "thing =", thing, "cmdline =", parts)
log.Log(INFO, "git ERROR. maybe okay err =", err)
if err.Error() == "exit status 1" {
log.Log(INFO, "git ERROR. normal exit status 1")
if parts[0] == "diff-index" {
log.Log(INFO, "git normal diff-index when repo dirty")
return nil, false, "git diff-index exit status 1"
}
}
}
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Error(err)
log.Warn("output was", string(output))
log.Warn("cmd exited with error", err)
// panic("fucknuts")
// The command failed (non-zero exit status)
if exitErr, ok := err.(*exec.ExitError); ok {
// Assert that it is an exec.ExitError and get the exit code
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
log.Warn("Exit Status: %d\n", status.ExitStatus())
}
} else {
log.Warn("cmd.Run() failed with %s\n", err)
}
return err, false, string(output)
}
tmp := string(output)
tmp = strings.TrimSpace(tmp)
// Print the output
return nil, true, tmp
}
// send the path and the command
func RunCmdRun(workingpath string, parts []string) error {
if len(parts) == 0 {
log.Warn("command line was empty")
return errors.New("empty")
}
if parts[0] == "" {
log.Warn("command line was empty")
return errors.New("empty")
}
thing := parts[0]
parts = parts[1:]
log.Log(INFO, "working path =", workingpath, "thing =", thing, "cmdline =", parts)
// Create the command
cmd := exec.Command(thing, parts...)
// Set the working directory
cmd.Dir = workingpath
// Execute the command
err := cmd.Run()
if err != nil {
log.Warn("ERROR working path =", workingpath, "thing =", thing, "cmdline =", parts)
log.Error(err)
log.Warn("cmd exited with error", err)
// panic("fucknuts")
// The command failed (non-zero exit status)
if exitErr, ok := err.(*exec.ExitError); ok {
// Assert that it is an exec.ExitError and get the exit code
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
log.Warn("Exit Status: %d\n", status.ExitStatus())
}
} else {
log.Warn("cmd.Run() failed with %s\n", err)
}
return err
}
return nil
}

171
shell.go
View File

@ -1,171 +0,0 @@
package shell
import (
"io/ioutil"
"os"
"os/exec"
"strings"
"time"
"go.wit.com/log"
)
// TODO: look at https://github.com/go-cmd/cmd/issues/20
// use go-cmd instead here?
var callback func(interface{}, int)
var shellStdout *os.File
var shellStderr *os.File
var spewOn bool = false
var quiet bool = false
// var msecDelay int = 20 // number of milliseconds to delay between reads with no data
// var bytesBuffer bytes.Buffer
// var bytesSplice []byte
func handleError(c interface{}, ret int) {
log.Log(INFO, "shell.Run() Returned", ret)
if callback != nil {
callback(c, ret)
}
}
func init() {
callback = nil
}
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 SpewOn() {
spewOn = true
}
func SetDelayInMsec(msecs int) {
msecDelay = msecs
}
func SetStdout(newout *os.File) {
shellStdout = newout
}
func SetStderr(newerr *os.File) {
shellStderr = newerr
}
func Unlink(filename string) {
os.Remove(Path(filename))
}
func RM(filename string) {
os.Remove(Path(filename))
}
func Daemon(cmdline string, timeout time.Duration) int {
for {
RunString(cmdline)
time.Sleep(timeout)
}
}
// run something and never return from it
// TODO: pass STDOUT, STDERR, STDIN correctly
// TODO: figure out how to nohup the process and exit
func Exec(cmdline string) {
log.Log(INFO, "shell.Run() START "+cmdline)
cmd := Chomp(cmdline) // this is like 'chomp' in perl
cmdArgs := strings.Fields(cmd)
process := exec.Command(cmdArgs[0], cmdArgs[1:len(cmdArgs)]...)
process.Stderr = os.Stderr
process.Stdin = os.Stdin
process.Stdout = os.Stdout
process.Start()
err := process.Wait()
log.Log(INFO, "shell.Exec() err =", err)
os.Exit(0)
}
func NewRun(workingpath string, cmd []string) error {
log.Log(INFO, "NewRun() ", cmd)
process := exec.Command(cmd[0], cmd[1:len(cmd)]...)
// Set the working directory
process.Dir = workingpath
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 err
}
// return true if the filename exists (cross-platform)
func Exists(filename string) bool {
_, err := os.Stat(Path(filename))
if os.IsNotExist(err) {
return false
}
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 {
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 Chomp(buffer)
}

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 package shell
import ( /*
"bufio"
"bytes"
"io"
"os/exec"
)
var FileMap map[string]*File var FileMap map[string]*File
var readBufferSize int var readBufferSize int
@ -54,3 +48,4 @@ func New() *OldShell {
return &tmp return &tmp
} }
*/

120
time.go Normal file
View File

@ -0,0 +1,120 @@
package shell
import (
"fmt"
"strconv"
"strings"
"time"
"go.wit.com/log"
)
// converts a git for-each-ref date. "Wed Feb 7 10:13:38 2024 -0600"
func getGitDateStamp(gitdefault string) (time.Time, string, string) {
// now := time.Now().Format("Wed Feb 7 10:13:38 2024 -0600")
const gitLayout = "Mon Jan 2 15:04:05 2006 -0700"
tagTime, err := time.Parse(gitLayout, gitdefault)
if err != nil {
log.Warn("GOT THIS IN PARSE AAA." + gitdefault + ".AAA")
log.Warn(err)
return time.Now(), "Feb 1 12:34:56 1978 -0600", ""
}
return tagTime, gitdefault, GetDurationStamp(tagTime)
}
func getRawDateStamp(raw string) (time.Time, string, string) {
parts := strings.Split(raw, " ")
if len(parts) == 0 {
// raw was blank here
// return "Jan 4 1977", "40y" // eh, why not. it'll be easy to grep for this
return time.Now(), "Jan 4 1977", "40y" // eh, why not. it'll be easy to grep for this
}
i, err := strconv.ParseInt(parts[0], 10, 64) // base 10 string, return int64
if err != nil {
log.Warn("Error converting timestamp:", raw)
log.Warn("Error converting timestamp err =", err)
return time.Now(), "", ""
}
// Parse the Unix timestamp into a time.Time object
gitTagDate := time.Unix(i, 0)
return gitTagDate, gitTagDate.UTC().Format("2006/01/02 15:04:05 UTC"), GetDurationStamp(gitTagDate)
}
func GetDurationStamp(t time.Time) string {
// Get the current time
currentTime := time.Now()
// Calculate the duration between t current time
duration := currentTime.Sub(t)
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 := ""
// check if it's more than a year
years := int(d.Hours()) / (24 * 365)
if years > 0 {
result += fmt.Sprintf("%dy ", years)
return result
}
// check if it's more than a day
days := int(d.Hours()) / 24
if days > 0 {
result += fmt.Sprintf("%dd ", days)
return result
}
// check if it's more than an hour
hours := int(d.Hours()) % 24
if hours > 0 {
result += fmt.Sprintf("%dh ", hours)
return result
}
// check if it's more than a minute
minutes := int(d.Minutes()) % 60
if minutes > 0 {
result += fmt.Sprintf("%dm ", minutes)
return result
}
// check if it's more than a second
seconds := int(d.Seconds()) % 60
if seconds > 0 {
result += fmt.Sprintf("%ds", seconds)
return result
}
// report in milliseconds
ms := int(d.Milliseconds())
if ms > 100 {
// todo: print .3s, etc ?
}
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) log.Log(INFO, "res.StatusCode: %d\n", resp.StatusCode)
if resp.StatusCode != 200 { if resp.StatusCode != 200 {
handleError(fmt.Errorf(fmt.Sprint("%d", resp.StatusCode)), -1) handleError(fmt.Errorf("%d", resp.StatusCode), -1)
return nil return nil
} }
@ -74,8 +74,7 @@ func WgetToFile(filepath string, url string) error {
// BUGS: The author's idea of friendly may differ to that of many other people. // BUGS: The author's idea of friendly may differ to that of many other people.
func Write(filepath string, data string) bool { func Write(filepath string, data string) bool {
// TODO: this isn't working for some reason and is making two '\n' chars // TODO: this isn't working for some reason and is making two '\n' chars
// probably because Chomp() isn't fixed yet data = strings.TrimSpace(data) + "\n"
data = Chomp(data) + "\n"
// Create the file // Create the file
ospath := Path(filepath) ospath := Path(filepath)
log.Log(INFO, "shell.Write() START ospath =", ospath, "filepath =", filepath) log.Log(INFO, "shell.Write() START ospath =", ospath, "filepath =", filepath)

View File

@ -3,11 +3,25 @@ package shell
import ( import (
"fmt" "fmt"
"os" "os"
"strings"
"go.wit.com/log" "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) { func scanToParent(pid int) (bool, string) {
ppid, err := GetPPID(pid) ppid, err := GetPPID(pid)
if err != nil { if err != nil {
@ -35,8 +49,6 @@ func scanToParent(pid int) (bool, string) {
case "make": case "make":
// keep digging for the parent xterm // keep digging for the parent xterm
return scanToParent(ppid) return scanToParent(ppid)
default:
return false, comm
} }
if comm == "bash" { if comm == "bash" {
} }
@ -81,8 +93,6 @@ func Shell() string {
switch envsh { switch envsh {
case "/bin/bash": case "/bin/bash":
return "bash" return "bash"
default:
return envsh
} }
return envsh return envsh
} }
@ -116,16 +126,17 @@ func XtermCmd(path string, cmd []string) {
// runs an xterm // runs an xterm
// waits until xterm exits // waits until xterm exits
func XtermCmdWait(path string, cmd []string) { 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 // keeps git diff from exiting on small diffs
os.Setenv("LESS", "-+F -+X -R") os.Setenv("LESS", "-+F -+X -R")
RunCmdRun(path, argsXterm) PathRunLog(path, cmd, INFO)
} }
/*
// spawns an xterm with something you can run at a command line // spawns an xterm with something you can run at a command line
// then executes bash // then executes bash
func XtermCmdBash(path string, cmd []string) { func XtermCmdBash(path string, cmd []string) {
@ -136,5 +147,6 @@ func XtermCmdBash(path string, cmd []string) {
bash += "'; bash\"" bash += "'; bash\""
tmp = append(argsXterm, "bash", bash) tmp = append(argsXterm, "bash", bash)
log.Info("XtermCmd() path =", path, "cmd =", tmp) log.Info("XtermCmd() path =", path, "cmd =", tmp)
go RunCmd(path, tmp) go PathRunLog(path, tmp, INFO)
} }
*/