Implement rudimentary 'ps' with --json flag.

--json mode doesn't have any fancy command line arguments yet. Just
outputs to raw JSON. Seems to work pretty well with Underscore. Also
added some structure; we can work out how the build system will work
later.
This commit is contained in:
Ian Adam Naval 2014-09-03 15:53:21 -04:00
parent 77f040729a
commit 9525516e37
4 changed files with 169 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
bin/
pkg/

22
src/jsh/common.go Normal file
View File

@ -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
}

38
src/jsh/process.go Normal file
View File

@ -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
}

106
src/jsh/ps/main.go Normal file
View File

@ -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()
}
}