From 7008e3d140d7f1800664293358516bec6b076eae Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sat, 22 Jun 2013 19:03:08 -0400 Subject: [PATCH] working on adding http support --- acc.go | 4 ++ am/manager.go | 114 ++++++++++++++++++++++++++++++++++++++++++++ args.go | 6 ++- http.go | 66 +++++++++++++++++++++++++ skeam.go | 3 ++ static/css/home.css | 15 ++++++ static/js/skeam.js | 1 + templates/home.html | 13 +++++ 8 files changed, 221 insertions(+), 1 deletion(-) create mode 100644 am/manager.go create mode 100644 http.go create mode 100644 static/css/home.css create mode 100644 static/js/skeam.js create mode 100644 templates/home.html diff --git a/acc.go b/acc.go index 80b3966..69b9199 100644 --- a/acc.go +++ b/acc.go @@ -17,6 +17,10 @@ type accumulator struct { floating bool } +// runs the accumulator accros the set of values, applying the accumulator's +// intFn and floatFn functions in order. It's basically just a left fold, and +// it starts using floatFn once the first float value is encountered, and then +// thereafter. func (a accumulator) total(vals []interface{}) (interface{}, error) { if vals == nil || len(vals) == 0 { return a.acc, nil diff --git a/am/manager.go b/am/manager.go new file mode 100644 index 0000000..51090bc --- /dev/null +++ b/am/manager.go @@ -0,0 +1,114 @@ +package am + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "strings" +) + +// an asset manager is responsible for managing references to files contained +// in source-controlled directories that associate to Go packages. I suspect +// that there's some overlap here with the standard library go/build package. +type Manager struct { + basePath string // base path on the local filesystem + baseURLPath string // base path for public-facing URLS +} + +// given an import path for a Go package, builds an asset manager for that +// package. Doesn't actually check if that package exists; this is kinda +// hazardous right now. ¯\_(ツ)_/¯ +func New(importPath, urlBase string) *Manager { + return &Manager{basePath: pkgDir(importPath), baseURLPath: urlBase} +} + +func (m *Manager) AbsPath(parts ...string) string { + return filepath.Join(m.basePath, filepath.Join(parts...)) +} + +func (m *Manager) Open(parts ...string) (*os.File, error) { + return os.Open(m.AbsPath(parts...)) +} + +func (m *Manager) URLPath(parts ...string) string { + return path.Join(m.baseURLPath, path.Join(parts...)) +} + +func (m *Manager) ReadFile(parts ...string) ([]byte, error) { + f, err := m.Open(parts...) + if err != nil { + return nil, err + } + defer f.Close() + return ioutil.ReadAll(f) +} + +// getPathDirs gets the user's GOPATH environment variable and splits it along +// the OS-specific path separator, returning a slice of strings, one per path +// directory. +func getPathDirs() []string { + path := os.Getenv("GOPATH") + if path == "" { + panic("am: GOPATH not set") + } + return strings.Split(path, string(os.PathListSeparator)) +} + +// getPkgDirCandidates gets the list of possible locations for a given import +// string. The paths are not guaranteed to exist or to even be valid; results +// are derived from the user's $GOPATH environment variable, which is not +// necessarily clean. +func getPkgDirCandidates(importPath string) []string { + pathDirs := getPathDirs() + importParts := strings.Split(importPath, "/") + candidates := make([]string, 0, len(pathDirs)) + for _, pathDir := range pathDirs { + srcDir := filepath.Join(pathDir, "src") + candidates = append(candidates, filepath.Join(srcDir, filepath.Join(importParts...))) + } + return candidates +} + +// existingDirectories accepts a slice of strings and returns a slice of +// strings representing which of the given strings corresponds to an existing +// directory. Note that this is prone to timing attacks, but it is presumed to +// not matter for this application; this may be an unsafe strategy to copy into +// other projects. +func existingDirectories(candidates []string) []string { + dirs := make([]string, 0, len(candidates)) + for _, path := range candidates { + if isDir(path) { + dirs = append(dirs, path) + } + } + return dirs +} + +// isDir takes a file path and returns a boolean representing whether the path +// is or is not a valid directory. Again, this is vulnerable to timing attacks +// and should be considered a Very Bad Idea in most contexts. +func isDir(path string) bool { + fi, err := os.Stat(path) + if err != nil { + return false + } + return fi.IsDir() +} + +// getPkgDir takes an import path and returns a string representing the +// directory path of the package on the current machine. This is generally a +// stupid thing to do, because we're generally not worried about the location +// of a package's source code files, since they may be out of sync with the +// actual binary, but in our case, we're using it to look up assets that have +// been made go-gettable. If no package dir can be found, an empty string is +// returned. The package may reasonably be installed into multiple workspaces. +// In this case, it's the first package found, as dictated by the user's +// $GOPATH environment variable. +func pkgDir(importPath string) string { + dirs := existingDirectories(getPkgDirCandidates(importPath)) + if len(dirs) == 0 { + return "" + } + return dirs[0] +} diff --git a/args.go b/args.go index acc5306..03a6399 100644 --- a/args.go +++ b/args.go @@ -8,9 +8,13 @@ import ( ) var ( - tcpAddr = flag.String("tcp", "", "tcp ip:port to listen on") + tcpAddr = flag.String("tcp", "", "tcp ip:port to listen on") + httpAddr = flag.String("http", "", "http ip:port to listen on") ) +// executes a file on disk using the universe environment. This will block +// until the entire file has been executed. Vals and errors printed to stdout +// and stderr, respectively. func runfile() { filename := flag.Args()[0] f, err := os.Open(filename) diff --git a/http.go b/http.go new file mode 100644 index 0000000..40d4795 --- /dev/null +++ b/http.go @@ -0,0 +1,66 @@ +package main + +import ( + "code.google.com/p/go.net/websocket" + "errors" + "fmt" + "github.com/jordanorelli/skeam/am" + "html/template" + "io" + "net/http" + "path/filepath" +) + +var assets = am.New("github.com/jordanorelli/skeam", "/static") + +func templatePath(relpath string) string { + return filepath.Join("templates", relpath) +} + +func getTemplate(relpath string) (*template.Template, error) { + p := templatePath(relpath) + b, err := assets.ReadFile(p) + if err != nil { + return nil, errors.New("skeam: unable to read template " + relpath) + } + + t := template.New(relpath).Funcs(template.FuncMap{ + "js": func(relpath string) (template.HTML, error) { + jspath := assets.URLPath("js", relpath) + return template.HTML(fmt.Sprintf(``, jspath)), nil + }, + "css": func(relpath string) (template.HTML, error) { + csspath := assets.URLPath("css", relpath) + return template.HTML(fmt.Sprintf(``, csspath)), nil + }, + }) + t, err = t.Parse(string(b)) + if err != nil { + return nil, errors.New("skeam: unable to parse template " + relpath + ": " + err.Error()) + } + return t, err +} + +type templateHandler string + +func (t templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + tpl, err := getTemplate(string(t)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if err := tpl.Execute(w, nil); err != nil { + fmt.Println(err.Error()) + } +} + +func wsHandler(ws *websocket.Conn) { + io.Copy(ws, ws) +} + +func runHTTPServer() { + http.Handle("/", templateHandler("home.html")) + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(assets.AbsPath("static"))))) + http.Handle("/ws", websocket.Handler(wsHandler)) + http.ListenAndServe(*httpAddr, nil) +} diff --git a/skeam.go b/skeam.go index 5d6e222..c99d767 100644 --- a/skeam.go +++ b/skeam.go @@ -205,6 +205,9 @@ func main() { if *tcpAddr != "" { runTCPServer() } + if *httpAddr != "" { + runHTTPServer() + } if len(flag.Args()) > 0 { runfile() return diff --git a/static/css/home.css b/static/css/home.css new file mode 100644 index 0000000..a608f04 --- /dev/null +++ b/static/css/home.css @@ -0,0 +1,15 @@ +html, body { + height: 100%; +} + +#bodywrap { + min-height: 100%; +} + +#txt-repl { + position: relative; + margin: -120px 0 0 0; + height: 110px; + width: 100%; + clear: both; +} diff --git a/static/js/skeam.js b/static/js/skeam.js new file mode 100644 index 0000000..5d861d9 --- /dev/null +++ b/static/js/skeam.js @@ -0,0 +1 @@ +console.log("oh hai"); diff --git a/templates/home.html b/templates/home.html new file mode 100644 index 0000000..427fa69 --- /dev/null +++ b/templates/home.html @@ -0,0 +1,13 @@ + + + {{css "home.css"}} + + +
+
+ + {{js "skeam.js"}} + +