Merge pull request #51 from Benaiah/config-file

Add configuration system
This commit is contained in:
Adam Tauber 2017-02-11 21:57:12 +01:00 committed by GitHub
commit 46994a8c6c
3 changed files with 160 additions and 13 deletions

85
config/config.go Normal file
View File

@ -0,0 +1,85 @@
package config
import (
"errors"
"os"
"path/filepath"
"runtime"
"time"
"github.com/BurntSushi/toml"
"github.com/mitchellh/go-homedir"
)
// Duration is used to automatically unmarshal timeout strings to
// time.Duration values
type Duration struct {
time.Duration
}
func (d *Duration) UnmarshalText(text []byte) error {
var err error
d.Duration, err = time.ParseDuration(string(text))
return err
}
type Config struct {
General GeneralOptions
Keys map[string]map[string]string
}
type GeneralOptions struct {
Timeout Duration
FormatJSON bool
PreserveScrollPosition bool
DefaultURLScheme string
}
var defaultTimeoutDuration, _ = time.ParseDuration("1m")
var DefaultConfig = Config{
General: GeneralOptions{
Timeout: Duration{
defaultTimeoutDuration,
},
FormatJSON: true,
PreserveScrollPosition: true,
DefaultURLScheme: "https",
},
}
func LoadConfig(configFile string) (*Config, error) {
if _, err := os.Stat(configFile); os.IsNotExist(err) {
return nil, errors.New("Config file does not exist.")
} else if err != nil {
return nil, err
}
conf := DefaultConfig
if _, err := toml.DecodeFile(configFile, &conf); err != nil {
return nil, err
}
return &conf, nil
}
func GetDefaultConfigLocation() string {
var configFolderLocation string
switch runtime.GOOS {
case "linux":
// Use the XDG_CONFIG_HOME variable if it is set, otherwise
// $HOME/.config/wuzz/config.toml
xdgConfigHome := os.Getenv("XDG_CONFIG_HOME")
if xdgConfigHome != "" {
configFolderLocation = xdgConfigHome
} else {
configFolderLocation, _ = homedir.Expand("~/.config/wuzz/")
}
default:
// On other platforms we just use $HOME/.wuzz
configFolderLocation, _ = homedir.Expand("~/.wuzz/")
}
return filepath.Join(configFolderLocation, "config.toml")
}

6
sample-config.toml Normal file
View File

@ -0,0 +1,6 @@
[general]
timeout = "1m"
defaultURLScheme = "https"
formatJSON = true
preserveScrollPosition = true

82
wuzz.go
View File

@ -19,6 +19,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/asciimoo/wuzz/config"
"github.com/jroimartin/gocui" "github.com/jroimartin/gocui"
"github.com/mattn/go-runewidth" "github.com/mattn/go-runewidth"
) )
@ -88,6 +90,7 @@ type App struct {
historyIndex int historyIndex int
currentPopup string currentPopup string
history []*Request history []*Request
config *config.Config
} }
type ViewEditor struct { type ViewEditor struct {
@ -201,7 +204,7 @@ func (a *App) Layout(g *gocui.Gui) error {
v.Editable = true v.Editable = true
v.Overwrite = false v.Overwrite = false
v.Editor = &singlelineEditor{&defaultEditor} v.Editor = &singlelineEditor{&defaultEditor}
setViewTextAndCursor(v, "https://") setViewTextAndCursor(v, a.config.General.DefaultURLScheme+"://")
} }
if v, err := g.SetView("get", 0, 3, splitX, splitY+1); err != nil { if v, err := g.SetView("get", 0, 3, splitX, splitY+1); err != nil {
if err != gocui.ErrUnknownView { if err != gocui.ErrUnknownView {
@ -425,7 +428,8 @@ func (a *App) SubmitRequest(g *gocui.Gui, _ *gocui.View) error {
} }
// pretty-print json // pretty-print json
if strings.Contains(response.Header.Get("Content-Type"), "application/json") { if strings.Contains(response.Header.Get("Content-Type"), "application/json") &&
a.config.General.FormatJSON {
var prettyJSON bytes.Buffer var prettyJSON bytes.Buffer
err := json.Indent(&prettyJSON, r.RawResponseBody, "", " ") err := json.Indent(&prettyJSON, r.RawResponseBody, "", " ")
if err == nil { if err == nil {
@ -831,7 +835,29 @@ func (a *App) restoreRequest(g *gocui.Gui, idx int) {
} }
func (a *App) ParseArgs(g *gocui.Gui) error { func (a *App) LoadConfig(configPath string) error {
if configPath == "" {
// Load config from default path
configPath = config.GetDefaultConfigLocation()
}
// If the config file doesn't exist, load the default config
if _, err := os.Stat(configPath); os.IsNotExist(err) {
a.config = &config.DefaultConfig
return nil
}
conf, err := config.LoadConfig(configPath)
if err != nil {
a.config = &config.DefaultConfig
return err
}
a.config = conf
return nil
}
func (a *App) ParseArgs(g *gocui.Gui, args []string) error {
a.Layout(g) a.Layout(g)
g.SetCurrentView(VIEWS[a.viewIndex]) g.SetCurrentView(VIEWS[a.viewIndex])
vheader, err := g.View("headers") vheader, err := g.View("headers")
@ -843,16 +869,16 @@ func (a *App) ParseArgs(g *gocui.Gui) error {
vget.Clear() vget.Clear()
add_content_type := false add_content_type := false
arg_index := 1 arg_index := 1
args_len := len(os.Args) args_len := len(args)
for arg_index < args_len { for arg_index < args_len {
arg := os.Args[arg_index] arg := args[arg_index]
switch arg { switch arg {
case "-H", "--header": case "-H", "--header":
if arg_index == args_len-1 { if arg_index == args_len-1 {
return errors.New("No header value specified") return errors.New("No header value specified")
} }
arg_index += 1 arg_index += 1
header := os.Args[arg_index] header := args[arg_index]
fmt.Fprintf(vheader, "%v\n", header) fmt.Fprintf(vheader, "%v\n", header)
case "-d", "--data": case "-d", "--data":
if arg_index == args_len-1 { if arg_index == args_len-1 {
@ -865,7 +891,7 @@ func (a *App) ParseArgs(g *gocui.Gui) error {
arg_index += 1 arg_index += 1
add_content_type = true add_content_type = true
data, _ := url.QueryUnescape(os.Args[arg_index]) data, _ := url.QueryUnescape(args[arg_index])
vdata, _ := g.View("data") vdata, _ := g.View("data")
setViewTextAndCursor(vdata, data) setViewTextAndCursor(vdata, data)
case "-X", "--request": case "-X", "--request":
@ -873,7 +899,7 @@ func (a *App) ParseArgs(g *gocui.Gui) error {
return errors.New("No HTTP method specified") return errors.New("No HTTP method specified")
} }
arg_index++ arg_index++
method := os.Args[arg_index] method := args[arg_index]
if method == "POST" || method == "PUT" { if method == "POST" || method == "PUT" {
add_content_type = true add_content_type = true
} }
@ -884,18 +910,18 @@ func (a *App) ParseArgs(g *gocui.Gui) error {
return errors.New("No timeout value specified") return errors.New("No timeout value specified")
} }
arg_index += 1 arg_index += 1
timeout, err := strconv.Atoi(os.Args[arg_index]) timeout, err := strconv.Atoi(args[arg_index])
if err != nil || timeout <= 0 { if err != nil || timeout <= 0 {
return errors.New("Invalid timeout value") return errors.New("Invalid timeout value")
} }
CLIENT.Timeout = time.Duration(timeout) * time.Millisecond a.config.General.Timeout = config.Duration{time.Duration(timeout) * time.Millisecond}
case "--compressed": case "--compressed":
vh, _ := g.View("headers") vh, _ := g.View("headers")
if strings.Index(getViewValue(g, "headers"), "Accept-Encoding") == -1 { if strings.Index(getViewValue(g, "headers"), "Accept-Encoding") == -1 {
fmt.Fprintln(vh, "Accept-Encoding: gzip, deflate") fmt.Fprintln(vh, "Accept-Encoding: gzip, deflate")
} }
default: default:
u := os.Args[arg_index] u := args[arg_index]
if strings.Index(u, "http://") != 0 && strings.Index(u, "https://") != 0 { if strings.Index(u, "http://") != 0 && strings.Index(u, "https://") != 0 {
u = "http://" + u u = "http://" + u
} }
@ -922,6 +948,12 @@ func (a *App) ParseArgs(g *gocui.Gui) error {
return nil return nil
} }
// Apply startup config values. This is run after a.ParseArgs, so that
// args can override the provided config values
func (a *App) InitConfig() {
CLIENT.Timeout = a.config.General.Timeout.Duration
}
func initApp(a *App, g *gocui.Gui) { func initApp(a *App, g *gocui.Gui) {
g.Cursor = true g.Cursor = true
g.InputEsc = false g.InputEsc = false
@ -995,7 +1027,9 @@ Key bindings:
} }
func main() { func main() {
for _, arg := range os.Args { configPath := ""
args := os.Args
for i, arg := range os.Args {
switch arg { switch arg {
case "-h", "--help": case "-h", "--help":
help() help()
@ -1003,6 +1037,12 @@ func main() {
case "-v", "--version": case "-v", "--version":
fmt.Printf("wuzz %v\n", VERSION) fmt.Printf("wuzz %v\n", VERSION)
return return
case "-c", "--config":
configPath = os.Args[i+1]
args = append(os.Args[:i], os.Args[i+2:]...)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
log.Fatal("Config file specified but does not exist: \"" + configPath + "\"")
}
} }
} }
g, err := gocui.NewGui(gocui.Output256) g, err := gocui.NewGui(gocui.Output256)
@ -1020,7 +1060,23 @@ func main() {
initApp(app, g) initApp(app, g)
err = app.ParseArgs(g) // load config (must be done *before* app.ParseArgs, as arguments
// should be able to override config values). An empty string passed
// to LoadConfig results in LoadConfig loading the default config
// location. If there is no config, the values in
// config.DefaultConfig will be used.
err = app.LoadConfig(configPath)
if err != nil {
g.Close()
log.Fatalf("Error loading config file: %v", err)
}
err = app.ParseArgs(g, args)
// Some of the values in the config need to have some startup
// behavior associated with them. This is run after ParseArgs so
// that command-line arguments can override configuration values.
app.InitConfig()
if err != nil { if err != nil {
g.Close() g.Close()