diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..042eb9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +bin/ +pkg/ + diff --git a/src/jsh/common.go b/src/jsh/common.go new file mode 100644 index 0000000..784eb5f --- /dev/null +++ b/src/jsh/common.go @@ -0,0 +1,22 @@ +/* Common structures and methods used across all coreutils */ + +package jsh + +import ( + "encoding/json" +) + +type JshOutput struct { + StdOut interface{} + StdErr interface{} +} + +// Converts a Jshoutput into a JSON string +func (j JshOutput) ToJson() *string { + jsonOut, err := json.Marshal(j) + if err != nil { + panic(err) + } + jsonString := string(jsonOut) + return &jsonString +} diff --git a/src/jsh/process.go b/src/jsh/process.go new file mode 100644 index 0000000..af2c0af --- /dev/null +++ b/src/jsh/process.go @@ -0,0 +1,38 @@ +/* File for Process-related structs and methods */ + +package jsh + +import ( + "errors" +) + +// Process is a struct for marshalling into JSON output for the ps utility. +type Process struct { + User string + Pid string + PercentCpu string + PercentMem string + Vsz string + Rss string + Tty string + Stat string + Start string + Time string + Command string +} + +// NewProcess +func NewProcess(args []string) (procPtr *Process, err error) { + err = nil + // 11 = number of fields in the Process struct + if len(args) != 11 { + procPtr = &Process{} + err = errors.New("Unexpected number of columns") + } else { + // TODO: figure out nicer way to do this + procPtr = &Process{ + args[0], args[1], args[2], args[3], args[4], args[5], + args[6], args[7], args[8], args[9], args[10]} + } + return +} diff --git a/src/jsh/ps/main.go b/src/jsh/ps/main.go new file mode 100644 index 0000000..9edfc2a --- /dev/null +++ b/src/jsh/ps/main.go @@ -0,0 +1,106 @@ +package main + +import ( + "flag" + "fmt" + "jsh" + "math" + "os" + "os/exec" + "strings" + "unicode" +) + +// FieldsN slices s into substrings after each instance of a whitespace +// character (according to unicode.IsSpace) and returns a slice of those +// substrings. The slice's length is guaranteed to be at most maxN, and +// all leftover fields are put into the last substring. +func FieldsN(s string, maxN int) []string { + // First count the fields. + n := 0 + inField := false + for _, rune := range s { + wasInField := inField + inField = !unicode.IsSpace(rune) + if inField && !wasInField { + n++ + } + } + n = int(math.Min(float64(n), float64(maxN))) + + // Now create them. + a := make([]string, n) + na := 0 + fieldStart := -1 // Set to -1 when looking for start of field. + for i, rune := range s { + if unicode.IsSpace(rune) && na+1 < maxN { + if fieldStart >= 0 { + a[na] = s[fieldStart:i] + na++ + fieldStart = -1 + } + } else if fieldStart == -1 { + fieldStart = i + } + } + if fieldStart >= 0 { // Last field might end at EOF. + a[na] = s[fieldStart:] + } + return a +} + +// Falls back to procps-ng passing the space-separated arguments in the given +// args slice. If args is nil, we default to the arguments passed to the command +// line using os.Args +func fallbackCompletelyWithArgs(args []string) *[]byte { + if args == nil { + args = os.Args[1:] + } + out, err := exec.Command("/usr/bin/ps", args...).Output() + if err != nil { + panic(err) + } + return &out +} + +// Falls back to procps-ng with no default arguments +func fallbackCompletely() *[]byte { + return fallbackCompletelyWithArgs(nil) +} + +// Converts raw output of "ps" into a slice of Process objects +func psOutputToProcesses(out string) *[]jsh.Process { + processes := []jsh.Process{} + lines := strings.Split(out, "\n") + header, procs := lines[0], lines[1:] + numFields := len(strings.Fields(header)) + + for _, proc := range procs { + p, err := jsh.NewProcess(FieldsN(proc, numFields)) + if err == nil { + processes = append(processes, *p) + } + } + return &processes +} + +func runJsonMode() { + // Run procps-ng "ps" with full output + psOut := string(*fallbackCompletelyWithArgs([]string{"auxww"})) + + processesPtr := psOutputToProcesses(psOut) + finalOut := jsh.JshOutput{*processesPtr, []string{}} + fmt.Printf("%s", *finalOut.ToJson()) +} + +func main() { + // Parse for JSON flag. + jsonModePtr := flag.Bool("json", false, "a bool") + flag.Parse() + + if !*jsonModePtr { + fmt.Printf("%s", fallbackCompletely()) + } else { + runJsonMode() + } +}