├── ui ├── .gitignore ├── fs_dev.go ├── fs.go ├── webpack.config.js ├── src │ ├── ducks.js │ ├── styles.css │ ├── index.js │ └── sidePanel.js └── package.json ├── vendor └── github.com │ ├── pkg │ └── browser │ │ ├── browser_darwin.go │ │ ├── browser_linux.go │ │ ├── browser_windows.go │ │ ├── browser_unsupported.go │ │ ├── browser_openbsd.go │ │ ├── example_test.go │ │ ├── examples │ │ └── Open │ │ │ └── main.go │ │ ├── README.md │ │ ├── LICENSE │ │ └── browser.go │ └── elazarl │ └── go-bindata-assetfs │ ├── doc.go │ ├── LICENSE │ ├── README.md │ ├── go-bindata-assetfs │ └── main.go │ └── assetfs.go ├── cmd └── ev │ ├── tmpl.go │ └── ev.go ├── Gopkg.lock ├── Gopkg.toml ├── README.md └── ev.go /ui/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser_darwin.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | func openBrowser(url string) error { 4 | return runCmd("open", url) 5 | } 6 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser_linux.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | func openBrowser(url string) error { 4 | return runCmd("xdg-open", url) 5 | } 6 | -------------------------------------------------------------------------------- /ui/fs_dev.go: -------------------------------------------------------------------------------- 1 | // +build dev 2 | 3 | package ui 4 | 5 | import "net/http" 6 | 7 | // FS serves the actual files from the local folder in dev mode. 8 | var FS = http.Dir("ui/dist/") 9 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser_windows.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func openBrowser(url string) error { 8 | r := strings.NewReplacer("&", "^&") 9 | return runCmd("cmd", "/c", "start", r.Replace(url)) 10 | } 11 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser_unsupported.go: -------------------------------------------------------------------------------- 1 | // +build !linux,!windows,!darwin,!openbsd 2 | 3 | package browser 4 | 5 | import ( 6 | "fmt" 7 | "runtime" 8 | ) 9 | 10 | func openBrowser(url string) error { 11 | return fmt.Errorf("openBrowser: unsupported operating system: %v", runtime.GOOS) 12 | } 13 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser_openbsd.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import ( 4 | "errors" 5 | "os/exec" 6 | ) 7 | 8 | func openBrowser(url string) error { 9 | err := runCmd("xdg-open", url) 10 | if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { 11 | return errors.New("xdg-open: command not found - install xdg-utils from ports(8)") 12 | } 13 | return err 14 | } 15 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/go-bindata-assetfs/doc.go: -------------------------------------------------------------------------------- 1 | // assetfs allows packages to serve static content embedded 2 | // with the go-bindata tool with the standard net/http package. 3 | // 4 | // See https://github.com/jteeuwen/go-bindata for more information 5 | // about embedding binary data with go-bindata. 6 | // 7 | // Usage example, after running 8 | // $ go-bindata data/... 9 | // use: 10 | // http.Handle("/", 11 | // http.FileServer( 12 | // &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "data"})) 13 | package assetfs 14 | -------------------------------------------------------------------------------- /cmd/ev/tmpl.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "html/template" 6 | ) 7 | 8 | func toJSON(v interface{}) template.JS { 9 | js, _ := json.Marshal(v) 10 | return template.JS(js) 11 | } 12 | 13 | const templateString = ` 14 | ev
15 | 16 | ` 17 | 18 | var indexTemplate = template.Must( 19 | template.New("index"). 20 | Funcs(template.FuncMap{"json": toJSON}). 21 | Parse(templateString)) 22 | -------------------------------------------------------------------------------- /ui/fs.go: -------------------------------------------------------------------------------- 1 | // +build !dev 2 | 3 | // Package ui generates binary data from the files inside the dist/ folder 4 | // to be included into the final binary, or to be served during development. 5 | package ui // import "gbbr.io/ev/ui" 6 | 7 | import assetfs "github.com/elazarl/go-bindata-assetfs" 8 | 9 | //go:generate webpack --display=errors-only 10 | //go:generate go-bindata -o bindata.go -prefix=dist/ -pkg ui dist/ 11 | 12 | // FS serves the binary embedded http.Filesystem. 13 | var FS = &assetfs.AssetFS{ 14 | Asset: Asset, 15 | AssetDir: AssetDir, 16 | AssetInfo: AssetInfo, 17 | Prefix: "", 18 | } 19 | -------------------------------------------------------------------------------- /Gopkg.lock: -------------------------------------------------------------------------------- 1 | # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. 2 | 3 | 4 | [[projects]] 5 | branch = "master" 6 | name = "github.com/elazarl/go-bindata-assetfs" 7 | packages = ["."] 8 | revision = "30f82fa23fd844bd5bb1e5f216db87fd77b5eb43" 9 | 10 | [[projects]] 11 | branch = "master" 12 | name = "github.com/pkg/browser" 13 | packages = ["."] 14 | revision = "c90ca0c84f15f81c982e32665bffd8d7aac8f097" 15 | 16 | [solve-meta] 17 | analyzer-name = "dep" 18 | analyzer-version = 1 19 | inputs-digest = "4c0d3af6fd91c6beccd2157dcf458de9561d00269f6595c084b14a6fa5fdcb6e" 20 | solver-name = "gps-cdcl" 21 | solver-version = 1 22 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/example_test.go: -------------------------------------------------------------------------------- 1 | package browser 2 | 3 | import "strings" 4 | 5 | func ExampleOpenFile() { 6 | OpenFile("index.html") 7 | } 8 | 9 | func ExampleOpenReader() { 10 | // https://github.com/rust-lang/rust/issues/13871 11 | const quote = `There was a night when winds from unknown spaces 12 | whirled us irresistibly into limitless vacum beyond all thought and entity. 13 | Perceptions of the most maddeningly untransmissible sort thronged upon us; 14 | perceptions of infinity which at the time convulsed us with joy, yet which 15 | are now partly lost to my memory and partly incapable of presentation to others.` 16 | r := strings.NewReader(quote) 17 | OpenReader(r) 18 | } 19 | 20 | func ExampleOpenURL() { 21 | const url = "http://golang.org/" 22 | OpenURL(url) 23 | } 24 | -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | 2 | # Gopkg.toml example 3 | # 4 | # Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md 5 | # for detailed Gopkg.toml documentation. 6 | # 7 | # required = ["github.com/user/thing/cmd/thing"] 8 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 9 | # 10 | # [[constraint]] 11 | # name = "github.com/user/project" 12 | # version = "1.0.0" 13 | # 14 | # [[constraint]] 15 | # name = "github.com/user/project2" 16 | # branch = "dev" 17 | # source = "github.com/myfork/project2" 18 | # 19 | # [[override]] 20 | # name = "github.com/x/y" 21 | # version = "2.4.0" 22 | 23 | 24 | [[constraint]] 25 | branch = "master" 26 | name = "github.com/elazarl/go-bindata-assetfs" 27 | 28 | [[constraint]] 29 | branch = "master" 30 | name = "github.com/pkg/browser" 31 | -------------------------------------------------------------------------------- /ui/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: './src/index.js', 5 | devtool: 'inline-source-map', 6 | 7 | devServer: { 8 | contentBase: './dist' 9 | }, 10 | 11 | resolve: { 12 | modules: ['src', 'node_modules'], 13 | }, 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.js$/, 19 | exclude: /(node_modules)/, 20 | use: { 21 | loader: 'babel-loader', 22 | options: { 23 | presets: ['env', 'react'], 24 | plugins: ['transform-object-rest-spread'] 25 | } 26 | } 27 | }, 28 | { 29 | test: /\.css$/, 30 | use: [ 31 | { loader: "style-loader" }, 32 | { loader: "css-loader" } 33 | ] 34 | } 35 | ] 36 | }, 37 | 38 | output: { 39 | filename: 'bundle.js', 40 | path: path.resolve(__dirname, 'dist'), 41 | publicPath: '/dist/' 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/examples/Open/main.go: -------------------------------------------------------------------------------- 1 | // Open is a simple example of the github.com/pkg/browser package. 2 | // 3 | // Usage: 4 | // 5 | // # Open a file in a browser window 6 | // Open $FILE 7 | // 8 | // # Open a URL in a browser window 9 | // Open $URL 10 | // 11 | // # Open the contents of stdin in a browser window 12 | // cat $SOMEFILE | Open 13 | package main 14 | 15 | import ( 16 | "flag" 17 | "fmt" 18 | "log" 19 | "os" 20 | 21 | "github.com/pkg/browser" 22 | ) 23 | 24 | func usage() { 25 | fmt.Fprintf(os.Stderr, "Usage:\n %s [file]\n", os.Args[0]) 26 | flag.PrintDefaults() 27 | } 28 | 29 | func init() { 30 | flag.Usage = usage 31 | flag.Parse() 32 | } 33 | 34 | func check(err error) { 35 | if err != nil { 36 | log.Fatal(err) 37 | } 38 | } 39 | 40 | func main() { 41 | args := flag.Args() 42 | switch len(args) { 43 | case 0: 44 | check(browser.OpenReader(os.Stdin)) 45 | case 1: 46 | check(browser.OpenFile(args[0])) 47 | default: 48 | usage() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ui/src/ducks.js: -------------------------------------------------------------------------------- 1 | // ducks: https://github.com/erikras/ducks-modular-redux 2 | 3 | export const next = () => ({type: "EV_NEXT"}); 4 | export const prev = () => ({type: "EV_PREV"}); 5 | export const goto = (i) => ({type: "EV_GOTO", payload: i}); 6 | 7 | export default function reducer(state = {}, action) { 8 | const {index, history} = state; 9 | 10 | switch (action.type) { 11 | case "EV_NEXT": 12 | if (index === history.length - 1) { 13 | return state; 14 | } 15 | return { 16 | ...state, 17 | index: index + 1 18 | }; 19 | case "EV_PREV": 20 | if (index === 0) { 21 | return state; 22 | } 23 | return { 24 | ...state, 25 | index: index - 1 26 | }; 27 | case "EV_GOTO": 28 | return { 29 | ...state, 30 | index: action.payload 31 | }; 32 | } 33 | return state; 34 | } 35 | -------------------------------------------------------------------------------- /ui/src/styles.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | overflow-y: scroll; 5 | } 6 | 7 | .d2h-tag.d2h-changed-tag { 8 | display: none; 9 | } 10 | 11 | .panel { 12 | position: fixed; 13 | top: 0; 14 | right: 0; 15 | width: 700px; 16 | height: auto; 17 | max-height: 100%; 18 | overflow-y: scroll; 19 | background: white; 20 | opacity: 0.8; 21 | border: 1px solid gray; 22 | padding: 10px; 23 | } 24 | 25 | .panel .nav { 26 | margin-bottom: 10px; 27 | user-select: none; 28 | } 29 | 30 | .panel .nav a { 31 | margin-right: 5px; 32 | } 33 | 34 | .panel .nav a.pull-right { 35 | float: right; 36 | } 37 | 38 | .panel.collapsed { 39 | height: 20px; 40 | overflow: hidden; 41 | } 42 | 43 | .panel.collapsed .details { 44 | display: none; 45 | } 46 | 47 | .panel #chart { 48 | height: 150px; 49 | margin: 15px 0; 50 | } 51 | 52 | a { 53 | cursor: pointer; 54 | color: #07c; 55 | } 56 | 57 | .txt pre { 58 | font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace; 59 | font-size: 14px; 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## ev 2 | explore the evolution of a function in your browser. 3 | 4 | ### installation 5 | 6 | ``` 7 | go get gbbr.io/ev/cmd/... 8 | ``` 9 | 10 | ### usage 11 | 12 | ``` 13 | usage: ev : 14 | ``` 15 | The command will open the browser showing snapshots of how the function `funcname` from `file` evolved in time throughout various git commits. I created it to better help me understand a codebase while trying to learn more about the implementation of Go's standard library. It can be used with any programming language. 16 | 17 | Below is an example screenshot viewing the `IndexAny` function from Go's `bytes` package. 18 | 19 | ![ev](http://i67.tinypic.com/2eatsfc.png) 20 | 21 | See a [demo](https://youtu.be/GqfDZX7xLUQ) of it, or try it out yourself! 22 | 23 | --- 24 | 25 | Note that `ev` uses `git log -L::` syntax, meaning that it also comes with its limitations. More specifically, if the file has multiple functions sharing the same name (ie. both method and function) it will only refer to the first occurrence starting from the top of the file. 26 | -------------------------------------------------------------------------------- /ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "contabi", 3 | "version": "beta", 4 | "description": "Contabi UI", 5 | "main": "index.js", 6 | "author": "Gabriel Aszalos", 7 | "license": "MIT", 8 | "scripts": { 9 | "start": "webpack-dev-server --open --history-api-fallback", 10 | "build": "webpack" 11 | }, 12 | "devDependencies": { 13 | "babel-core": "^6.25.0", 14 | "babel-eslint": "^7.2.3", 15 | "babel-loader": "^7.1.1", 16 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 17 | "babel-preset-env": "^1.6.0", 18 | "babel-preset-react": "^6.24.1", 19 | "css-loader": "^0.28.4", 20 | "eslint": "^4.2.0", 21 | "eslint-loader": "^1.9.0", 22 | "eslint-plugin-import": "^2.7.0", 23 | "eslint-plugin-react": "^7.1.0", 24 | "style-loader": "^0.18.2", 25 | "webpack": "^3.3.0", 26 | "webpack-dev-server": "^2.5.1" 27 | }, 28 | "dependencies": { 29 | "c3": "^0.4.18", 30 | "classnames": "^2.2.5", 31 | "diff2html": "^2.3.0", 32 | "moment": "^2.18.1", 33 | "react": "^15.6.1", 34 | "react-dom": "^15.6.1", 35 | "react-redux": "^5.0.5", 36 | "redux": "^3.0.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ui/src/index.js: -------------------------------------------------------------------------------- 1 | import './styles.css'; 2 | import 'diff2html/dist/diff2html.css'; 3 | 4 | import React, {Component} from 'react'; 5 | import {render} from 'react-dom'; 6 | import reducer from './ducks'; 7 | import {Provider} from 'react-redux'; 8 | import {createStore} from 'redux'; 9 | import {Diff2Html as diff} from 'diff2html'; 10 | import SidePanel from './sidePanel'; 11 | import {connect} from 'react-redux'; 12 | 13 | class DiffViewComponent extends Component { 14 | render() { 15 | const __html = diff.getPrettyHtml(this.props.diff); 16 | return
; 17 | } 18 | } 19 | 20 | const DiffView = connect(({history, index}) => ({diff: history[index].Diff}))(DiffViewComponent); 21 | 22 | document.addEventListener('DOMContentLoaded', () => { 23 | const store = createStore(reducer, {index: 0, history: window.GIT_HISTORY}); 24 | 25 | render( 26 | 27 |
28 | 29 | 30 |
31 |
, 32 | document.getElementById('app-root') 33 | ); 34 | }); 35 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/README.md: -------------------------------------------------------------------------------- 1 | 2 | # browser 3 | import "github.com/pkg/browser" 4 | 5 | Package browser provides helpers to open files, readers, and urls in a browser window. 6 | 7 | The choice of which browser is started is entirely client dependant. 8 | 9 | 10 | 11 | 12 | 13 | ## Variables 14 | ``` go 15 | var Stderr io.Writer = os.Stderr 16 | ``` 17 | Stderr is the io.Writer to which executed commands write standard error. 18 | 19 | ``` go 20 | var Stdout io.Writer = os.Stdout 21 | ``` 22 | Stdout is the io.Writer to which executed commands write standard output. 23 | 24 | 25 | ## func OpenFile 26 | ``` go 27 | func OpenFile(path string) error 28 | ``` 29 | OpenFile opens new browser window for the file path. 30 | 31 | 32 | ## func OpenReader 33 | ``` go 34 | func OpenReader(r io.Reader) error 35 | ``` 36 | OpenReader consumes the contents of r and presents the 37 | results in a new browser window. 38 | 39 | 40 | ## func OpenURL 41 | ``` go 42 | func OpenURL(url string) error 43 | ``` 44 | OpenURL opens a new browser window pointing to url. 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | - - - 55 | Generated by [godoc2md](http://godoc.org/github.com/davecheney/godoc2md) 56 | -------------------------------------------------------------------------------- /cmd/ev/ev.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "gbbr.io/ev" 11 | "gbbr.io/ev/ui" 12 | "github.com/pkg/browser" 13 | ) 14 | 15 | var funcName, fileName string 16 | var parsedLog []*ev.Commit 17 | 18 | func init() { 19 | log.SetFlags(0) 20 | log.SetPrefix("ev: ") 21 | if len(os.Args) <= 1 { 22 | usageAndExit() 23 | } 24 | parts := strings.Split(os.Args[1], ":") 25 | if len(parts) != 2 { 26 | usageAndExit() 27 | } 28 | funcName, fileName = parts[0], parts[1] 29 | } 30 | 31 | func usageAndExit() { 32 | fmt.Println(`usage: ev :`) 33 | os.Exit(0) 34 | } 35 | 36 | func index(w http.ResponseWriter, req *http.Request) { 37 | if err := indexTemplate.Execute(w, parsedLog); err != nil { 38 | log.Fatal(err) 39 | } 40 | } 41 | 42 | func main() { 43 | var err error 44 | parsedLog, err = ev.Log(funcName, fileName) 45 | if err != nil { 46 | log.Fatal(err) 47 | } 48 | mux := http.NewServeMux() 49 | mux.Handle("/dist/", http.StripPrefix("/dist/", http.FileServer(ui.FS))) 50 | mux.HandleFunc("/", index) 51 | go func() { 52 | if err := http.ListenAndServe(":8888", mux); err != nil { 53 | log.Fatal(err) 54 | } 55 | }() 56 | browser.OpenURL("http://localhost:8888") 57 | select {} 58 | } 59 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/go-bindata-assetfs/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Elazar Leibovich 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Dave Cheney 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/go-bindata-assetfs/README.md: -------------------------------------------------------------------------------- 1 | # go-bindata-assetfs 2 | 3 | Serve embedded files from [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) with `net/http`. 4 | 5 | [GoDoc](http://godoc.org/github.com/elazarl/go-bindata-assetfs) 6 | 7 | ### Installation 8 | 9 | Install with 10 | 11 | $ go get github.com/jteeuwen/go-bindata/... 12 | $ go get github.com/elazarl/go-bindata-assetfs/... 13 | 14 | ### Creating embedded data 15 | 16 | Usage is identical to [jteeuwen/go-bindata](https://github.com/jteeuwen/go-bindata) usage, 17 | instead of running `go-bindata` run `go-bindata-assetfs`. 18 | 19 | The tool will create a `bindata_assetfs.go` file, which contains the embedded data. 20 | 21 | A typical use case is 22 | 23 | $ go-bindata-assetfs data/... 24 | 25 | ### Using assetFS in your code 26 | 27 | The generated file provides an `assetFS()` function that returns a `http.Filesystem` 28 | wrapping the embedded files. What you usually want to do is: 29 | 30 | http.Handle("/", http.FileServer(assetFS())) 31 | 32 | This would run an HTTP server serving the embedded files. 33 | 34 | ## Without running binary tool 35 | 36 | You can always just run the `go-bindata` tool, and then 37 | 38 | use 39 | 40 | import "github.com/elazarl/go-bindata-assetfs" 41 | ... 42 | http.Handle("/", 43 | http.FileServer( 44 | &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: AssetInfo, Prefix: "data"})) 45 | 46 | to serve files embedded from the `data` directory. 47 | -------------------------------------------------------------------------------- /vendor/github.com/pkg/browser/browser.go: -------------------------------------------------------------------------------- 1 | // Package browser provides helpers to open files, readers, and urls in a browser window. 2 | // 3 | // The choice of which browser is started is entirely client dependant. 4 | package browser 5 | 6 | import ( 7 | "fmt" 8 | "io" 9 | "io/ioutil" 10 | "os" 11 | "os/exec" 12 | "path/filepath" 13 | ) 14 | 15 | // Stdout is the io.Writer to which executed commands write standard output. 16 | var Stdout io.Writer = os.Stdout 17 | 18 | // Stderr is the io.Writer to which executed commands write standard error. 19 | var Stderr io.Writer = os.Stderr 20 | 21 | // OpenFile opens new browser window for the file path. 22 | func OpenFile(path string) error { 23 | path, err := filepath.Abs(path) 24 | if err != nil { 25 | return err 26 | } 27 | return OpenURL("file://" + path) 28 | } 29 | 30 | // OpenReader consumes the contents of r and presents the 31 | // results in a new browser window. 32 | func OpenReader(r io.Reader) error { 33 | f, err := ioutil.TempFile("", "browser") 34 | if err != nil { 35 | return fmt.Errorf("browser: could not create temporary file: %v", err) 36 | } 37 | if _, err := io.Copy(f, r); err != nil { 38 | f.Close() 39 | return fmt.Errorf("browser: caching temporary file failed: %v", err) 40 | } 41 | if err := f.Close(); err != nil { 42 | return fmt.Errorf("browser: caching temporary file failed: %v", err) 43 | } 44 | oldname := f.Name() 45 | newname := oldname + ".html" 46 | if err := os.Rename(oldname, newname); err != nil { 47 | return fmt.Errorf("browser: renaming temporary file failed: %v", err) 48 | } 49 | return OpenFile(newname) 50 | } 51 | 52 | // OpenURL opens a new browser window pointing to url. 53 | func OpenURL(url string) error { 54 | return openBrowser(url) 55 | } 56 | 57 | func runCmd(prog string, args ...string) error { 58 | cmd := exec.Command(prog, args...) 59 | cmd.Stdout = Stdout 60 | cmd.Stderr = Stderr 61 | return cmd.Run() 62 | } 63 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "strings" 11 | ) 12 | 13 | const bindatafile = "bindata.go" 14 | 15 | func isDebug(args []string) bool { 16 | flagset := flag.NewFlagSet("", flag.ContinueOnError) 17 | debug := flagset.Bool("debug", false, "") 18 | debugArgs := make([]string, 0) 19 | for _, arg := range args { 20 | if strings.HasPrefix(arg, "-debug") { 21 | debugArgs = append(debugArgs, arg) 22 | } 23 | } 24 | flagset.Parse(debugArgs) 25 | if debug == nil { 26 | return false 27 | } 28 | return *debug 29 | } 30 | 31 | func main() { 32 | if _, err := exec.LookPath("go-bindata"); err != nil { 33 | fmt.Println("Cannot find go-bindata executable in path") 34 | fmt.Println("Maybe you need: go get github.com/elazarl/go-bindata-assetfs/...") 35 | os.Exit(1) 36 | } 37 | cmd := exec.Command("go-bindata", os.Args[1:]...) 38 | cmd.Stdin = os.Stdin 39 | cmd.Stdout = os.Stdout 40 | cmd.Stderr = os.Stderr 41 | if err := cmd.Run(); err != nil { 42 | os.Exit(1) 43 | } 44 | in, err := os.Open(bindatafile) 45 | if err != nil { 46 | fmt.Fprintln(os.Stderr, "Cannot read", bindatafile, err) 47 | return 48 | } 49 | out, err := os.Create("bindata_assetfs.go") 50 | if err != nil { 51 | fmt.Fprintln(os.Stderr, "Cannot write 'bindata_assetfs.go'", err) 52 | return 53 | } 54 | debug := isDebug(os.Args[1:]) 55 | r := bufio.NewReader(in) 56 | done := false 57 | for line, isPrefix, err := r.ReadLine(); err == nil; line, isPrefix, err = r.ReadLine() { 58 | if !isPrefix { 59 | line = append(line, '\n') 60 | } 61 | if _, err := out.Write(line); err != nil { 62 | fmt.Fprintln(os.Stderr, "Cannot write to 'bindata_assetfs.go'", err) 63 | return 64 | } 65 | if !done && !isPrefix && bytes.HasPrefix(line, []byte("import (")) { 66 | if debug { 67 | fmt.Fprintln(out, "\t\"net/http\"") 68 | } else { 69 | fmt.Fprintln(out, "\t\"github.com/elazarl/go-bindata-assetfs\"") 70 | } 71 | done = true 72 | } 73 | } 74 | if debug { 75 | fmt.Fprintln(out, ` 76 | func assetFS() http.FileSystem { 77 | for k := range _bintree.Children { 78 | return http.Dir(k) 79 | } 80 | panic("unreachable") 81 | }`) 82 | } else { 83 | fmt.Fprintln(out, ` 84 | func assetFS() *assetfs.AssetFS { 85 | assetInfo := func(path string) (os.FileInfo, error) { 86 | return os.Stat(path) 87 | } 88 | for k := range _bintree.Children { 89 | return &assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, AssetInfo: assetInfo, Prefix: k} 90 | } 91 | panic("unreachable") 92 | }`) 93 | } 94 | // Close files BEFORE remove calls (don't use defer). 95 | in.Close() 96 | out.Close() 97 | if err := os.Remove(bindatafile); err != nil { 98 | fmt.Fprintln(os.Stderr, "Cannot remove", bindatafile, err) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ev.go: -------------------------------------------------------------------------------- 1 | package ev // import "gbbr.io/ev" 2 | import ( 3 | "bufio" 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "io" 8 | "os/exec" 9 | "strconv" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | // Commit holds an entry inside the git log output. 15 | type Commit struct { 16 | SHA string 17 | AuthorName string 18 | AuthorEmail string 19 | AuthorDate time.Time 20 | CommitterName string 21 | CommitterEmail string 22 | CommitterDate time.Time 23 | Msg string 24 | Diff string 25 | Changes int 26 | } 27 | 28 | // logReader executes the `git log -L::` command with a custom format 29 | // and returns an io.Reader which can read from the output. 30 | func logReader(re, fn string) (io.Reader, error) { 31 | cmd := exec.Command("git", "log", 32 | fmt.Sprintf("-L^:%s:%s", re, fn), 33 | `--date=unix`, 34 | `--pretty=format:HEADER:%H,%an,%ae,%ad,%cn,%ce,%cd%n%b%nEV_BODY_END`) 35 | var stdout, stderr bytes.Buffer 36 | cmd.Stdout = &stdout 37 | cmd.Stderr = &stderr 38 | if err := cmd.Run(); err != nil { 39 | return nil, errors.New(stderr.String()) 40 | } 41 | return &stdout, nil 42 | } 43 | 44 | // Log parses the result of the `git log -L::` command and returns 45 | // a slice of commits. They are ordered in descending chronological order 46 | // and show the history of the function (or regexp) `re` inside the file `fn`. 47 | func Log(re, fn string) ([]*Commit, error) { 48 | r, err := logReader(re, fn) 49 | if err != nil { 50 | return nil, err 51 | } 52 | scn := bufio.NewScanner(r) 53 | list := make([]*Commit, 0) 54 | var ( 55 | c *Commit 56 | diff bytes.Buffer 57 | msg bytes.Buffer 58 | ) 59 | readingDiff := false 60 | for scn.Scan() { 61 | line := scn.Text() 62 | if strings.HasPrefix(line, "HEADER:") { 63 | readingDiff = false 64 | if c != nil { 65 | c.Diff = diff.String() 66 | c.Msg = msg.String() 67 | list = append(list, c) 68 | } 69 | c = new(Commit) 70 | err := readHeader(line[7:], c) 71 | if err != nil { 72 | return nil, err 73 | } 74 | diff.Truncate(0) 75 | msg.Truncate(0) 76 | continue 77 | } 78 | if line == "EV_BODY_END" { 79 | readingDiff = true 80 | continue 81 | } 82 | if readingDiff { 83 | if len(line) >= 1 && (line[0] == '-' || line[0] == '+') { 84 | c.Changes++ 85 | } 86 | diff.WriteString(line) 87 | diff.WriteString("\r\n") 88 | } else { 89 | msg.WriteString(line) 90 | msg.WriteString("\r\n") 91 | } 92 | } 93 | if err := scn.Err(); err != nil { 94 | return nil, fmt.Errorf("parse: %s", err) 95 | } 96 | return list, nil 97 | } 98 | 99 | // epoch converts s to time.Time. s is expected to hold the number of 100 | // seconds since the epoch time. 101 | func epoch(s string) time.Time { 102 | i, err := strconv.ParseInt(s, 10, 64) 103 | if err != nil { 104 | return time.Now() 105 | } 106 | return time.Unix(i, 0) 107 | } 108 | 109 | // readHeader reads a git log header into c. 110 | func readHeader(line string, c *Commit) error { 111 | p := strings.Split(line, ",") 112 | if len(p) != 7 { 113 | return fmt.Errorf("bad header: %s\n", line) 114 | } 115 | c.SHA, c.AuthorName, c.AuthorEmail, c.AuthorDate, 116 | c.CommitterName, c.CommitterEmail, c.CommitterDate = 117 | p[0], p[1], p[2], epoch(p[3]), p[4], p[5], epoch(p[6]) 118 | return nil 119 | } 120 | -------------------------------------------------------------------------------- /ui/src/sidePanel.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {next, prev, goto} from './ducks'; 3 | import {connect} from 'react-redux'; 4 | import moment from 'moment'; 5 | import 'c3/c3.min.css'; 6 | import c3 from 'c3'; 7 | 8 | class SidePanel extends Component { 9 | constructor(props) { 10 | super(props); 11 | 12 | this.state = {collapsed: false}; 13 | this.toggleCollapsed = this.toggleCollapsed.bind(this); 14 | this.chart = null; 15 | } 16 | 17 | toggleCollapsed() { 18 | this.setState({collapsed: !this.state.collapsed}); 19 | } 20 | 21 | componentDidMount() { 22 | let {history, goto, total} = this.props; 23 | let changes = ['Lines Changed']; 24 | let dates = ['x']; 25 | 26 | history.forEach(({Changes, CommitterDate}) => { 27 | changes.push(Changes); 28 | dates.push(moment(CommitterDate).toDate()); 29 | }); 30 | 31 | this.chart = c3.generate({ 32 | bindto: '#chart', 33 | data: { 34 | x: 'x', 35 | columns: [dates, changes], 36 | onclick: ({index}) => goto(total - index - 1), 37 | selection: { 38 | enabled: true, 39 | multiple: false 40 | } 41 | }, 42 | axis: { 43 | x: { 44 | type: 'timeseries', 45 | tick: { 46 | format: '%d-%m-%Y %H:%M' 47 | } 48 | } 49 | } 50 | }); 51 | 52 | this.selectIndex(0); 53 | } 54 | 55 | selectIndex(i) { 56 | this.chart.select(null, [this.props.total - i - 1], true); 57 | } 58 | 59 | componentDidUpdate(prevProps) { 60 | const {index} = this.props; 61 | 62 | if (prevProps.index !== index && this.chart !== null) { 63 | this.selectIndex(index) 64 | } 65 | } 66 | 67 | render() { 68 | const {next, prev, total, entry, index} = this.props; 69 | const {collapsed} = this.state; 70 | 71 | return ( 72 |
73 |
74 | 75 | 76 | 77 | {collapsed ? 'Show' : 'Hide'} 78 | 79 |
80 |
81 |
SHA: {entry.SHA}
82 |
83 | Author: {entry.AuthorName} <{entry.AuthorEmail}> 84 |  {moment(entry.AuthorDate).fromNow()} 85 |
86 |
87 | Committer: {entry.CommitterName} <{entry.CommitterEmail}> 88 |  {moment(entry.CommitterDate).fromNow()} 89 |
90 |
91 |
92 |
{entry.Msg}
93 |
94 | ); 95 | } 96 | } 97 | 98 | export default connect(({history, index}) => ({ 99 | entry: history[index], 100 | total: history.length, 101 | history, 102 | index 103 | }), {next, prev, goto})(SidePanel); 104 | -------------------------------------------------------------------------------- /vendor/github.com/elazarl/go-bindata-assetfs/assetfs.go: -------------------------------------------------------------------------------- 1 | package assetfs 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | "os" 10 | "path" 11 | "path/filepath" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | var ( 17 | defaultFileTimestamp = time.Now() 18 | ) 19 | 20 | // FakeFile implements os.FileInfo interface for a given path and size 21 | type FakeFile struct { 22 | // Path is the path of this file 23 | Path string 24 | // Dir marks of the path is a directory 25 | Dir bool 26 | // Len is the length of the fake file, zero if it is a directory 27 | Len int64 28 | // Timestamp is the ModTime of this file 29 | Timestamp time.Time 30 | } 31 | 32 | func (f *FakeFile) Name() string { 33 | _, name := filepath.Split(f.Path) 34 | return name 35 | } 36 | 37 | func (f *FakeFile) Mode() os.FileMode { 38 | mode := os.FileMode(0644) 39 | if f.Dir { 40 | return mode | os.ModeDir 41 | } 42 | return mode 43 | } 44 | 45 | func (f *FakeFile) ModTime() time.Time { 46 | return f.Timestamp 47 | } 48 | 49 | func (f *FakeFile) Size() int64 { 50 | return f.Len 51 | } 52 | 53 | func (f *FakeFile) IsDir() bool { 54 | return f.Mode().IsDir() 55 | } 56 | 57 | func (f *FakeFile) Sys() interface{} { 58 | return nil 59 | } 60 | 61 | // AssetFile implements http.File interface for a no-directory file with content 62 | type AssetFile struct { 63 | *bytes.Reader 64 | io.Closer 65 | FakeFile 66 | } 67 | 68 | func NewAssetFile(name string, content []byte, timestamp time.Time) *AssetFile { 69 | if timestamp.IsZero() { 70 | timestamp = defaultFileTimestamp 71 | } 72 | return &AssetFile{ 73 | bytes.NewReader(content), 74 | ioutil.NopCloser(nil), 75 | FakeFile{name, false, int64(len(content)), timestamp}} 76 | } 77 | 78 | func (f *AssetFile) Readdir(count int) ([]os.FileInfo, error) { 79 | return nil, errors.New("not a directory") 80 | } 81 | 82 | func (f *AssetFile) Size() int64 { 83 | return f.FakeFile.Size() 84 | } 85 | 86 | func (f *AssetFile) Stat() (os.FileInfo, error) { 87 | return f, nil 88 | } 89 | 90 | // AssetDirectory implements http.File interface for a directory 91 | type AssetDirectory struct { 92 | AssetFile 93 | ChildrenRead int 94 | Children []os.FileInfo 95 | } 96 | 97 | func NewAssetDirectory(name string, children []string, fs *AssetFS) *AssetDirectory { 98 | fileinfos := make([]os.FileInfo, 0, len(children)) 99 | for _, child := range children { 100 | _, err := fs.AssetDir(filepath.Join(name, child)) 101 | fileinfos = append(fileinfos, &FakeFile{child, err == nil, 0, time.Time{}}) 102 | } 103 | return &AssetDirectory{ 104 | AssetFile{ 105 | bytes.NewReader(nil), 106 | ioutil.NopCloser(nil), 107 | FakeFile{name, true, 0, time.Time{}}, 108 | }, 109 | 0, 110 | fileinfos} 111 | } 112 | 113 | func (f *AssetDirectory) Readdir(count int) ([]os.FileInfo, error) { 114 | if count <= 0 { 115 | return f.Children, nil 116 | } 117 | if f.ChildrenRead+count > len(f.Children) { 118 | count = len(f.Children) - f.ChildrenRead 119 | } 120 | rv := f.Children[f.ChildrenRead : f.ChildrenRead+count] 121 | f.ChildrenRead += count 122 | return rv, nil 123 | } 124 | 125 | func (f *AssetDirectory) Stat() (os.FileInfo, error) { 126 | return f, nil 127 | } 128 | 129 | // AssetFS implements http.FileSystem, allowing 130 | // embedded files to be served from net/http package. 131 | type AssetFS struct { 132 | // Asset should return content of file in path if exists 133 | Asset func(path string) ([]byte, error) 134 | // AssetDir should return list of files in the path 135 | AssetDir func(path string) ([]string, error) 136 | // AssetInfo should return the info of file in path if exists 137 | AssetInfo func(path string) (os.FileInfo, error) 138 | // Prefix would be prepended to http requests 139 | Prefix string 140 | } 141 | 142 | func (fs *AssetFS) Open(name string) (http.File, error) { 143 | name = path.Join(fs.Prefix, name) 144 | if len(name) > 0 && name[0] == '/' { 145 | name = name[1:] 146 | } 147 | if b, err := fs.Asset(name); err == nil { 148 | timestamp := defaultFileTimestamp 149 | if fs.AssetInfo != nil { 150 | if info, err := fs.AssetInfo(name); err == nil { 151 | timestamp = info.ModTime() 152 | } 153 | } 154 | return NewAssetFile(name, b, timestamp), nil 155 | } 156 | if children, err := fs.AssetDir(name); err == nil { 157 | return NewAssetDirectory(name, children, fs), nil 158 | } else { 159 | // If the error is not found, return an error that will 160 | // result in a 404 error. Otherwise the server returns 161 | // a 500 error for files not found. 162 | if strings.Contains(err.Error(), "not found") { 163 | return nil, os.ErrNotExist 164 | } 165 | return nil, err 166 | } 167 | } 168 | --------------------------------------------------------------------------------