mirror of
https://github.com/asciimoo/wuzz
synced 2024-11-10 13:10:29 +00:00
Merge pull request #51 from Benaiah/config-file
Add configuration system
This commit is contained in:
commit
46994a8c6c
85
config/config.go
Normal file
85
config/config.go
Normal 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
6
sample-config.toml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
[general]
|
||||||
|
|
||||||
|
timeout = "1m"
|
||||||
|
defaultURLScheme = "https"
|
||||||
|
formatJSON = true
|
||||||
|
preserveScrollPosition = true
|
82
wuzz.go
82
wuzz.go
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user