├── .gitignore ├── LICENSE ├── README.md ├── cmd ├── cal │ └── cal.go ├── cat │ └── cat.go ├── date │ └── date.go ├── echo │ └── echo.go ├── mkdir │ └── mkdir.go └── touch │ └── touch.go └── tool.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | goblin 2 | ====== 3 | 4 | plan9 inspired command line commands implemented in pure Go 5 | 6 | 7 | ### Installation: 8 | `go get github.com/mortdeus/goblin && goblin install` 9 | 10 | While running the `goblin install` command, the goblin tool will 11 | chdir into each './cmd/$tool' directory in order to run 12 | os.Exec("go build") and compile each tool from source. 13 | If the tool is sucessfully compiled into an executable, 14 | the go tool then moves that binary to $GOTOOLDIR. 15 | ##### Caution: 16 | At the moment there isn't a mechanism in place that checks 17 | whether the tools in $GOTOOLDIR, (specifically the tools that don't belong to 18 | goblin), have filenames that clash with goblin's tools. 19 | 20 | Therefore `goblin install`'s current behavior is to just throw out the old tools 21 | and replace them with the new. In most cases this shouldn't be an issue 22 | because goblin will not install any goblin tools that would clobber a 23 | standard go tool. (i.e `"*g", "*l", "*a", "*c", "yacc", "objdump" etc) 24 | 25 | 26 | 27 | ### Usage: 28 | `go tool $cmd` 29 | 30 | run `go tool` to get the list of tools you can call with go tool. 31 | 32 | 33 | 34 | ### License: 35 | Same license used for Go. 36 | http://golang.org/LICENSE 37 | ##### Notice: 38 | While this code is definitely inspired by plan9's tools, these 39 | tools are not a direct c to go source **port** of the original plan9 tools. 40 | 41 | All goblin code is reimplemented a new code from the ground up, Therefore 42 | goblin software is not legally tied the Lucent Public License, or any 43 | other licenses that plan9/inferno software has been licensed and distributed 44 | under. 45 | 46 | Futhermore, any code that is contributed to goblin may be rejected if 47 | it's blantantly obvious that the code being commited to goblin is a 48 | derivation of plan9's source code. 49 | 50 | 51 | To learn more about the reasoning behind this policy: 52 | 53 | http://directory.fsf.org/wiki/License:LucentPLv1.02 54 | 55 | -------------------------------------------------------------------------------- /cmd/cal/cal.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strconv" 7 | "time" 8 | ) 9 | 10 | var ( 11 | daystr = [...]string{ 12 | "##", "01", "02", "03", "04", "05", "06", "07", 13 | "08", "09", "10", "11", "12", "13", "14", 14 | "15", "16", "17", "18", "19", "20", "21", 15 | "22", "23", "24", "25", "26", "27", "28", 16 | "29", "30", "31", 17 | } 18 | monthstr = [...]string{"", 19 | "January %-d", "February %-d", "March %-d", "April %-d", 20 | "May %-d", "June %-d", "July %-d", " August %-d", "September %-d", 21 | "October %-d", "November %-d", "December %-d", 22 | } 23 | daysInMonth = [...]int{0, 24 | 31, 29, 31, 30, 25 | 31, 30, 31, 31, 26 | 30, 31, 30, 31, 27 | } 28 | 29 | today = time.Now() 30 | ) 31 | 32 | type year int 33 | type month struct { 34 | days []byte 35 | first int 36 | offset int 37 | } 38 | 39 | const calTemplate = "\n" + 40 | " ]====================================[ \n" + 41 | " | Su | Mo | Tu | We | Th | Fr | Sa | \n" + 42 | " ]====================================[ \n" + 43 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 44 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 45 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 46 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 47 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 48 | " | %s | %s | %s | %s | %s | %s | %s | \n" + 49 | " ]====================================[ \n" + 50 | " < %-s >\n" + 51 | " ]====================================[ \n" 52 | 53 | func main() { 54 | cal(procFlags()) 55 | } 56 | 57 | func cal(m int, y year) { 58 | y.numDaysPerMonth() 59 | var b = make([]byte, 0) 60 | 61 | janPrefix := [...][]byte{ 62 | []byte{}, 63 | []byte{31}, 64 | []byte{30, 31}, 65 | []byte{29, 30, 31}, 66 | []byte{28, 29, 30, 31}, 67 | []byte{27, 28, 29, 30, 31}, 68 | []byte{26, 27, 28, 29, 30, 31}, 69 | } 70 | first := y.jan1() 71 | b = append(b, janPrefix[first]...) 72 | 73 | offset := first 74 | months := make([]month, 12) 75 | for mo, ndays := range daysInMonth { 76 | if mo == 0 { 77 | continue 78 | } 79 | for i := 0; i < ndays; i++ { 80 | b = append(b, byte(i+1)) 81 | } 82 | s := make([]byte, ndays+first) 83 | v := copy(s, b[offset-first:offset+ndays]) 84 | for i := 0; i < 42-v; i++ { 85 | 86 | s = append(s, byte(i+1)) 87 | 88 | } 89 | 90 | offset += ndays 91 | mon := month{s, first, offset} 92 | first = (first + ndays) % 7 93 | months[mo-1] = mon 94 | } 95 | tyear, tmonth, tday := today.Date() 96 | for i, mon := range months { 97 | if tyear == int(y) && i+1 == int(tmonth) { 98 | mon.days[(mon.first+tday)-1] = 0 99 | } 100 | if i+1 == m || m == 0 { 101 | fmt.Printf(calTemplate, 102 | daystr[mon.days[0]], daystr[mon.days[1]], daystr[mon.days[2]], daystr[mon.days[3]], daystr[mon.days[4]], daystr[mon.days[5]], daystr[mon.days[6]], 103 | daystr[mon.days[7]], daystr[mon.days[8]], daystr[mon.days[9]], daystr[mon.days[10]], daystr[mon.days[11]], daystr[mon.days[12]], daystr[mon.days[13]], 104 | daystr[mon.days[14]], daystr[mon.days[15]], daystr[mon.days[16]], daystr[mon.days[17]], daystr[mon.days[18]], daystr[mon.days[19]], daystr[mon.days[20]], 105 | daystr[mon.days[21]], daystr[mon.days[22]], daystr[mon.days[23]], daystr[mon.days[24]], daystr[mon.days[25]], daystr[mon.days[26]], daystr[mon.days[27]], 106 | daystr[mon.days[28]], daystr[mon.days[29]], daystr[mon.days[30]], daystr[mon.days[31]], daystr[mon.days[32]], daystr[mon.days[33]], daystr[mon.days[34]], 107 | daystr[mon.days[35]], daystr[mon.days[36]], daystr[mon.days[37]], daystr[mon.days[38]], daystr[mon.days[39]], daystr[mon.days[40]], daystr[mon.days[41]], 108 | fmt.Sprintf(monthstr[i+1], y)) 109 | } 110 | } 111 | 112 | } 113 | 114 | func fatal(err error) { 115 | fmt.Fprintf(os.Stderr, "%s:\t%s\n", "cal", err.Error()) 116 | os.Exit(2) 117 | } 118 | 119 | func procFlags() (m int, y year) { 120 | args := os.Args[1:] 121 | y = year(today.Year()) 122 | 123 | if len(args) == 0 { 124 | m = int(today.Month()) 125 | return 126 | } 127 | 128 | for _, a := range args { 129 | if m == 0 && 130 | (a[0] >= 'A' && a[0] <= 'z' || 131 | (len(a) <= 2 && a[0] >= '0' && a[0] <= '9')) { 132 | 133 | if a[0] < 'a' { 134 | a = string(a[0]+'a'-'A') + a[1:] 135 | } 136 | if len(a) > 1 && a[0] < 'A' { 137 | switch a[1] { 138 | case 0: 139 | case 1: 140 | case 2: 141 | default: 142 | goto YEAR 143 | } 144 | } 145 | switch a { 146 | case "jan", "january", "1": 147 | m = 1 148 | case "feb", "february", "2": 149 | m = 2 150 | case "mar", "march", "3": 151 | m = 3 152 | case "apr", "april", "4": 153 | m = 4 154 | case "may", "5": 155 | m = 5 156 | case "jun", "june", "6": 157 | m = 6 158 | case "jul", "july", "7": 159 | m = 7 160 | case "aug", "august", "8": 161 | m = 8 162 | case "sep", "september", "9": 163 | m = 9 164 | case "oct", "october", "10": 165 | m = 10 166 | case "nov", "november", "11": 167 | m = 11 168 | case "dec", "december", "12": 169 | m = 12 170 | default: 171 | fatal(fmt.Errorf("Invalid month argument value: %s", a)) 172 | } 173 | continue 174 | } 175 | YEAR: 176 | if len(a) <= 4 && a[0] >= '1' && a[0] <= '9' { 177 | 178 | for _, s := range a { 179 | if s < '0' || s > '9' { 180 | fatal(fmt.Errorf("Invalid year argument value: %s", a)) 181 | } 182 | } 183 | if tmp, err := strconv.Atoi(a); err != nil { 184 | fatal(err) 185 | } else { 186 | y = year(tmp) 187 | } 188 | 189 | } else { 190 | 191 | fatal(fmt.Errorf("Invalid argument value: %s", a)) 192 | } 193 | } 194 | return 195 | } 196 | 197 | func (yr year) numDaysPerMonth() { 198 | d := yr.jan1() 199 | switch (year(int(yr)+1).jan1() + (7 - d)) % 7 { 200 | case 2: 201 | case 1: 202 | daysInMonth[2] = 28 203 | //1752 204 | default: 205 | daysInMonth[9] = 19 206 | } 207 | } 208 | 209 | // return day of the week 210 | // of jan 1 of given year 211 | func (yr year) jan1() int { 212 | 213 | // normal gregorian calendar 214 | // one extra day per four years 215 | y := int(yr) 216 | d := 4 + y + (y+3)/4 217 | 218 | // julian calendar 219 | // regular gregorian 220 | // less three days per 400 221 | 222 | if y > 1800 { 223 | d -= (y - 1701) / 100 224 | d += (y - 1601) / 400 225 | } 226 | 227 | // great calendar changeover instant 228 | 229 | if y > 1752 { 230 | d += 3 231 | } 232 | 233 | return d % 7 234 | } 235 | -------------------------------------------------------------------------------- /cmd/cat/cat.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io" 7 | "os" 8 | ) 9 | 10 | var cmd = struct{ name, flags string }{ 11 | "cat", 12 | "[ file ... ]", 13 | } 14 | 15 | func usage() { 16 | fmt.Fprintln(os.Stderr, "Usage:", cmd.name, cmd.flags) 17 | flag.PrintDefaults() 18 | os.Exit(2) 19 | } 20 | 21 | func fatal(err error) { 22 | fmt.Fprintf(os.Stderr, "%s: %s\n", cmd.name, err) 23 | os.Exit(1) 24 | } 25 | 26 | func main() { 27 | flag.Usage = usage 28 | flag.Parse() 29 | args := flag.Args() 30 | if len(args) == 0 { 31 | cat(os.Stdin) 32 | return 33 | } 34 | for _, s := range args { 35 | f, err := os.Open(s) 36 | if err != nil { 37 | fatal(err) 38 | } 39 | cat(f) 40 | } 41 | 42 | } 43 | 44 | func cat(f io.Reader) { 45 | b := make([]byte, 8*1024) 46 | for { 47 | n, err := f.Read(b) 48 | if n == 0 { 49 | return 50 | } 51 | if err != nil { 52 | fatal(err) 53 | } 54 | if n, err = os.Stdout.Write(b[:n]); err != nil { 55 | fatal(err) 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /cmd/date/date.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "strconv" 8 | "time" 9 | ) 10 | 11 | var ( 12 | cmd = struct{ name, flags string }{ 13 | "date", 14 | "[ -u ] [ -n ] [ seconds ]", 15 | } 16 | nflag = flag.Bool("n", false, "print as number of seconds since epoch") 17 | uflag = flag.Bool("u", false, "print UTC") 18 | ) 19 | 20 | func usage() { 21 | fmt.Fprintln(os.Stderr, "Usage", cmd.name, cmd.flags) 22 | flag.PrintDefaults() 23 | os.Exit(2) 24 | } 25 | 26 | func fatal(err error) { 27 | fmt.Fprintf(os.Stderr, "%s: %s\n", cmd.name, err) 28 | os.Exit(1) 29 | } 30 | 31 | func main() { 32 | flag.Usage = usage 33 | flag.Parse() 34 | args := flag.Args() 35 | var now time.Time 36 | if len(args) == 0 { 37 | now = time.Now() 38 | } else if len(args) != 1 { 39 | usage() 40 | } else { 41 | s, err := strconv.ParseInt(args[0], 0, 64) 42 | if err != nil { 43 | fatal(fmt.Errorf("bad number: %s", err)) 44 | } 45 | now = time.Unix(s, 0) 46 | } 47 | if *nflag { 48 | fmt.Printf("%d\n", now.Unix()) 49 | } else if *uflag { 50 | fmt.Printf("%s\n", now.UTC().Format("Mon Jan 2 15:04:05 MST 2006")) 51 | } else { 52 | fmt.Printf("%s\n", now.Format("Mon Jan 2 15:04:05 MST 2006")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /cmd/echo/echo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var ( 10 | cmd = struct{ name, flags string }{ 11 | "echo", 12 | "[ -n ] [ arg ...]", 13 | } 14 | nflag = flag.Bool("n", false, "suppress newline") 15 | ) 16 | 17 | func usage() { 18 | fmt.Fprintln(os.Stderr, "Usage:", cmd.name, cmd.flags) 19 | flag.PrintDefaults() 20 | os.Exit(2) 21 | } 22 | 23 | func main() { 24 | flag.Usage = usage 25 | flag.Parse() 26 | args := flag.Args() 27 | for _, arg := range args { 28 | fmt.Printf("%v", arg) 29 | } 30 | if !*nflag { 31 | fmt.Print("\n") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /cmd/mkdir/mkdir.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | ) 8 | 9 | var ( 10 | cmd = struct{ name, flags string }{ 11 | "mkdir", 12 | "[-p] [-m mode] dir...", 13 | } 14 | mkParents = flag.Bool("p", false, "create necessary parent directories") 15 | mode = flag.Int("m", 0777, "permissions for new directory") 16 | ) 17 | 18 | func usage() { 19 | fmt.Fprintln(os.Stderr, "Usage:", cmd.name, cmd.flags) 20 | flag.PrintDefaults() 21 | os.Exit(2) 22 | } 23 | 24 | func main() { 25 | flag.Usage = usage 26 | flag.Parse() 27 | 28 | m := os.FileMode(*mode) 29 | 30 | for _, a := range flag.Args() { 31 | var err error 32 | if *mkParents { 33 | err = os.MkdirAll(a, m) 34 | } else { 35 | err = os.Mkdir(a, m) 36 | } 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "%s: %s\n", cmd.name, err) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /cmd/touch/touch.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "time" 8 | ) 9 | 10 | var ( 11 | cmd = struct{ name, flags string }{ 12 | "touch", 13 | "[ -c ] [ -t time] files...", 14 | } 15 | dontCreate = flag.Bool("c", false, "don't create non-existend file") 16 | newt = flag.Int64("t", time.Now().Unix(), "new modification time") 17 | ) 18 | 19 | func fatal(err error) { 20 | fmt.Fprintf(os.Stderr, "%s: %s\n", cmd.name, err) 21 | os.Exit(1) 22 | } 23 | 24 | func main() { 25 | flag.Usage = usage 26 | flag.Parse() 27 | 28 | var t time.Time 29 | if newt != nil { 30 | t = time.Unix(*newt, 0) 31 | } else { 32 | t = time.Now() 33 | } 34 | 35 | for _, a := range flag.Args() { 36 | err := os.Chtimes(a, t, t) // ??? We set the atime too 37 | 38 | if os.IsNotExist(err) { 39 | if *dontCreate { 40 | fatal(err) 41 | } 42 | f, err := os.Create(a) 43 | if err != nil { 44 | f.Close() 45 | } 46 | 47 | if err == nil && newt != nil { 48 | err = os.Chtimes(a, t, t) 49 | } 50 | 51 | if err != nil { 52 | fatal(err) 53 | } 54 | } 55 | } 56 | } 57 | 58 | func usage() { 59 | fmt.Fprintln(os.Stderr, "Usage:", cmd.name, cmd.flags) 60 | flag.PrintDefaults() 61 | os.Exit(2) 62 | } 63 | -------------------------------------------------------------------------------- /tool.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "go/build" 6 | "log" 7 | "os" 8 | "os/exec" 9 | //"runtime" 10 | "path" 11 | "strings" 12 | ) 13 | 14 | func init() { 15 | log.SetPrefix("goblin:\t ") 16 | log.SetFlags(0) 17 | } 18 | 19 | func getPkgPath() (string, error) { 20 | p, err := exec.LookPath(os.Args[0]) 21 | if err != nil { 22 | return "", err 23 | } 24 | cmd := exec.Command("go", "tool", "objdump", "-s=main.main", p) 25 | out, err := cmd.Output() 26 | if err != nil { 27 | return "", err 28 | } 29 | 30 | s := strings.SplitN(fmt.Sprintf("%s", out), "\n", 2)[0] 31 | s = s[len("TEXT main.main(SB) "):] 32 | 33 | //makesure this source file's name matches whats hardcoded here. 34 | // Otherwise things break. 35 | return s[:len(s)-len("/tool.go")], nil 36 | } 37 | 38 | func usage() (s string) { 39 | install := "install:\tinstalls the goblin tools to $GOTOOLDIR" 40 | return fmt.Sprintln(install) 41 | } 42 | 43 | func main() { 44 | if len(os.Args) == 1 { 45 | log.Println(usage()) 46 | return 47 | } 48 | for _, arg := range os.Args[1:] { 49 | switch arg { 50 | case "install": 51 | install() 52 | default: 53 | log.Println("invalid input arg:", arg) 54 | log.Println(usage()) 55 | return 56 | } 57 | } 58 | } 59 | 60 | func install() { 61 | if p, err := getPkgPath(); err != nil { 62 | log.Fatal(err) 63 | } else { 64 | if err := os.Chdir(p); err != nil { 65 | log.Fatal(err) 66 | } 67 | } 68 | 69 | if err := os.Chdir("cmd"); err != nil { 70 | log.Fatal(err) 71 | } 72 | f, err := os.Open(".") 73 | if err != nil { 74 | log.Fatal(err) 75 | } 76 | dirs, err := f.Readdirnames(0) 77 | if err != nil { 78 | log.Fatal(err) 79 | } 80 | for _, d := range dirs { 81 | if err := os.Chdir(d); err != nil { 82 | log.Fatal(err) 83 | } 84 | if d, err := os.Getwd(); err != nil { 85 | log.Fatal(err) 86 | } else { 87 | log.Println(d) 88 | nm := path.Base(d) 89 | out, err := exec.Command("go", "build").CombinedOutput() 90 | if err != nil { 91 | log.Printf("go build %s failed\n\n", nm) 92 | fmt.Fprintf(os.Stderr, "[go-build-log]\n%s", out) 93 | goto quit 94 | } 95 | 96 | log.Printf("mv %s to $GOTOOLDIR\n", nm) 97 | if err := os.Rename(d+"/"+nm, build.ToolDir+"/"+nm); err != nil { 98 | log.Fatal(err) 99 | } 100 | } 101 | quit: 102 | if err := os.Chdir(".."); err != nil { 103 | log.Fatal(err) 104 | } 105 | fmt.Println() 106 | } 107 | } 108 | --------------------------------------------------------------------------------