First implementation of config file

Includes change of CLI args, many of been moved to the config file and
those that remain begin with `--` not `-` and may be worded differently.

Touches #37
This commit is contained in:
Thomas Buckley-Houston 2018-07-17 18:43:52 +08:00
parent e048fc8d6a
commit ef18913e3c
11 changed files with 216 additions and 56 deletions

64
interfacer/Gopkg.lock generated
View file

@ -13,6 +13,12 @@
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
name = "github.com/fsnotify/fsnotify"
packages = ["."]
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
branch = "master"
name = "github.com/gdamore/encoding"
@ -37,18 +43,36 @@
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/hashicorp/hcl"
packages = [".","hcl/ast","hcl/parser","hcl/printer","hcl/scanner","hcl/strconv","hcl/token","json/parser","json/scanner","json/token"]
revision = "ef8a98b0bbce4a65b5aa4c368430a80ddc533168"
[[projects]]
name = "github.com/lucasb-eyer/go-colorful"
packages = ["."]
revision = "345fbb3dbcdb252d9985ee899a84963c0fa24c82"
version = "v1.0"
[[projects]]
name = "github.com/magiconair/properties"
packages = ["."]
revision = "c2353362d570a7bfa228149c62842019201cfb71"
version = "v1.8.0"
[[projects]]
name = "github.com/mattn/go-runewidth"
packages = ["."]
revision = "9e777a8366cce605130a531d2cd6363d07ad7317"
version = "v0.0.2"
[[projects]]
branch = "master"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
revision = "f15292f7a699fcc1a38a80977f80a046874ba8ac"
[[projects]]
name = "github.com/onsi/ginkgo"
packages = [".","config","internal/codelocation","internal/containernode","internal/failer","internal/leafnodes","internal/remote","internal/spec","internal/spec_iterator","internal/specrunner","internal/suite","internal/testingtproxy","internal/writer","reporters","reporters/stenographer","reporters/stenographer/support/go-colorable","reporters/stenographer/support/go-isatty","types"]
@ -61,6 +85,12 @@
revision = "62bff4df71bdbc266561a0caee19f0594b17c240"
version = "v1.4.0"
[[projects]]
name = "github.com/pelletier/go-toml"
packages = ["."]
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
@ -79,6 +109,36 @@
packages = ["."]
revision = "e180dbdc8da04c4fa04272e875ce64949f38bd3e"
[[projects]]
name = "github.com/spf13/afero"
packages = [".","mem"]
revision = "787d034dfe70e44075ccc060d346146ef53270ad"
version = "v1.1.1"
[[projects]]
name = "github.com/spf13/cast"
packages = ["."]
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
branch = "master"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
revision = "7c0cea34c8ece3fbeb2b27ab9b59511d360fb394"
[[projects]]
name = "github.com/spf13/pflag"
packages = ["."]
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
name = "github.com/spf13/viper"
packages = ["."]
revision = "b5e8006cbee93ec955a89ab31e0e3ce3204f3736"
version = "v1.0.2"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert","require"]
@ -105,7 +165,7 @@
[[projects]]
name = "golang.org/x/text"
packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/utf8internal","language","runes","transform","unicode/cldr"]
packages = ["encoding","encoding/charmap","encoding/htmlindex","encoding/internal","encoding/internal/identifier","encoding/japanese","encoding/korean","encoding/simplifiedchinese","encoding/traditionalchinese","encoding/unicode","internal/gen","internal/tag","internal/triegen","internal/ucd","internal/utf8internal","language","runes","transform","unicode/cldr","unicode/norm"]
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
@ -118,6 +178,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "acff811653ac14fa3fc60de5ffe1b15b5889b5f93a73863398bff1585a675589"
inputs-digest = "a758e47b231056184a2433d9870401e6d2a15fc69c7f9b35780bd57fbc0fc24e"
solver-name = "gps-cdcl"
solver-version = 1

View file

@ -2,7 +2,6 @@ package browsh
import (
"encoding/base64"
"flag"
"fmt"
"io/ioutil"
"os"
@ -17,7 +16,8 @@ import (
"github.com/gdamore/tcell"
"github.com/go-errors/errors"
"github.com/shibukawa/configdir"
"github.com/spf13/viper"
"github.com/spf13/pflag"
)
var (
@ -35,20 +35,6 @@ var (
****///////////////////
********///////////////
***********************`
webSocketPort = flag.String("websocket-port", "3334", "Web socket service address")
firefoxBinary = flag.String("firefox", "firefox", "Path to Firefox executable")
isFFGui = flag.Bool("with-gui", false, "Don't use headless Firefox")
isUseExistingFirefox = flag.Bool("use-existing-ff", false, "Whether Browsh should launch Firefox or not")
useFFProfile = flag.String("ff-profile", "default", "Firefox profile to use")
isDebug = flag.Bool("debug", false, "Log to ./debug.log")
timeLimit = flag.Int("time-limit", 0, "Kill Browsh after the specified number of seconds")
// StartupURL is the URL of the first tab at boot
StartupURL = flag.String("startup-url", "https://google.com", "URL to launch at startup")
// IsHTTPServer needs to be exported for use in tests
IsHTTPServer = flag.Bool("http-server", false, "Run as an HTTP service")
// HTTPServerPort also needs to be exported for use in tests
HTTPServerPort = flag.String("http-server-port", "4333", "HTTP server address")
httpServerBind = flag.String("http-server-bind", "0.0.0.0", "HTTP server binding address")
// IsTesting is used in tests, so it needs to be exported
IsTesting = false
logfile string
@ -75,7 +61,7 @@ func Log(msg string) {
if !*isDebug {
return
}
if *IsHTTPServer && !IsTesting {
if viper.GetBool("http-server-mode") && !IsTesting {
fmt.Println(msg)
} else {
f, oErr := os.OpenFile(logfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600)
@ -98,10 +84,15 @@ func initialise() {
if *isDebug {
setupLogging()
}
loadConfig()
}
// Shutdown tries its best to cleanly shutdown browsh and the associated browser
func Shutdown(err error) {
if *isDebug {
out := err.(*errors.Error).ErrorStack()
Log(fmt.Sprintf(out))
}
exitCode := 0
if screen != nil {
screen.Fini()
@ -110,10 +101,6 @@ func Shutdown(err error) {
exitCode = 1
println(err.Error())
}
if *isDebug {
out := err.(*errors.Error).ErrorStack()
Log(fmt.Sprintf(out))
}
os.Exit(exitCode)
}
@ -141,14 +128,6 @@ func saveScreenshot(base64String string) {
file.Close()
}
// Gets a cross-platform path to store Browsh config
func getConfigFolder() string {
configDirs := configdir.New("browsh", "firefox_profile")
folders := configDirs.QueryFolders(configdir.Global)
folders[0].MkdirAll()
return folders[0].Path
}
// Shell provides nice and easy shell commands
func Shell(command string) string {
parts := strings.Fields(command)
@ -210,8 +189,8 @@ func ttyEntry() {
// MainEntry decides between running Browsh as a CLI app or as an HTTP web server
func MainEntry() {
flag.Parse()
if *IsHTTPServer {
pflag.Parse()
if viper.GetBool("http-server-mode") {
HTTPServerStart()
} else {
ttyEntry()

View file

@ -7,6 +7,7 @@ import (
"strings"
"github.com/gorilla/websocket"
"github.com/spf13/viper"
)
var (
@ -27,7 +28,8 @@ type incomingRawText struct {
func startWebSocketServer() {
serverMux := http.NewServeMux()
serverMux.HandleFunc("/", webSocketServer)
if err := http.ListenAndServe(":"+*webSocketPort, serverMux); err != nil {
port := viper.GetString("browsh.websocket-port")
if err := http.ListenAndServe(":"+port, serverMux); err != nil {
Shutdown(err)
}
}
@ -61,7 +63,7 @@ func webSocketReader(ws *websocket.Conn) {
func handleWebextensionCommand(message []byte) {
parts := strings.Split(string(message), ",")
command := parts[0]
if *IsHTTPServer {
if viper.GetBool("http-server-mode") {
handleRawFrameTextCommands(parts)
return
}
@ -139,12 +141,12 @@ func webSocketServer(w http.ResponseWriter, r *http.Request) {
isConnectedToWebExtension = true
go webSocketWriter(ws)
go webSocketReader(ws)
if *IsHTTPServer {
if viper.GetBool("http-server-mode") {
sendMessageToWebExtension("/raw_text_mode")
} else {
sendTtySize()
}
// For some reason, using Firefox's CLI arg `--url https://google.com` doesn't consistently
// work. So we do it here instead.
sendMessageToWebExtension("/new_tab," + *StartupURL)
sendMessageToWebExtension("/new_tab," + viper.GetString("startup-url"))
}

View file

@ -0,0 +1,93 @@
package browsh
import (
"fmt"
"os"
"path/filepath"
"strings"
"bytes"
"github.com/shibukawa/configdir"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
var (
configFilename = "config.toml"
isDebug = pflag.Bool("debug", false, "Log to ./debug.log")
timeLimit = pflag.Int("time-limit", 0, "Kill Browsh after the specified number of seconds")
_ = pflag.Bool("http-server-mode", false, "Run as an HTTP service")
_ = pflag.String("startup-url", "https://google.com", "URL to launch at startup")
_ = pflag.String("firefox.path", "firefox", "Path to Firefox executable")
_ = pflag.Bool("firefox.with-gui", false, "Don't use headless Firefox")
_ = pflag.Bool("firefox.use-existing", false, "Whether Browsh should launch Firefox or not")
)
func getConfigNamespace() string {
if IsTesting {
return "browsh-testing"
}
return "browsh"
}
// Gets a cross-platform path to a folder containing Browsh config
func getConfigDir() string {
marker := "browsh-settings"
// configdir has no other option but to have a nested folder
configDirs := configdir.New(getConfigNamespace(), marker)
folders := configDirs.QueryFolders(configdir.Global)
// Delete the previously enforced nested folder
path := strings.Trim(folders[0].Path, marker)
os.MkdirAll(path, os.ModePerm)
ensureConfigFile(path)
return path
}
// Copy the sample config file if the user doesn't already have a config file
func ensureConfigFile(path string) {
fullPath := filepath.Join(path, configFilename)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
file, err := os.Create(fullPath)
if err != nil {
Shutdown(err)
}
defer file.Close()
_, err = file.WriteString(configSample)
if err != nil {
Shutdown(err)
}
}
}
// Gets a cross-platform path to store a Browsh-specific Firefox profile
func getFirefoxProfilePath() string {
configDirs := configdir.New(getConfigNamespace(), "firefox_profile")
folders := configDirs.QueryFolders(configdir.Global)
folders[0].MkdirAll()
return folders[0].Path
}
func loadConfig() {
dir := getConfigDir()
fullPath := filepath.Join(dir, configFilename)
Log("Looking in " + fullPath + " for config.")
viper.SetConfigType("toml")
viper.SetConfigName(strings.Trim(configFilename, ".toml"))
viper.AddConfigPath(dir)
viper.AddConfigPath(".")
// First load the sample config in case the user hasn't updated any new fields
err := viper.ReadConfig(bytes.NewBuffer([]byte(configSample)))
if err != nil {
Shutdown(err)
}
// Then load the users own config file, overwriting the sample config
err = viper.MergeInConfig()
if err != nil {
Shutdown(err)
}
viper.BindPFlags(pflag.CommandLine)
Log("Using the folowing config values:")
Log(fmt.Sprintf("%v", viper.AllSettings()))
}

View file

@ -0,0 +1,16 @@
package browsh
var configSample =
`[browsh]
websocket-port = 3334
[firefox]
path = "firefox"
profile = "default"
use-existing = false
with-gui = false
[http-server]
port = 4333
bind = "0.0.0.0"
`

View file

@ -15,6 +15,7 @@ import (
"github.com/gdamore/tcell"
"github.com/go-errors/errors"
"github.com/spf13/viper"
)
var (
@ -58,18 +59,19 @@ func startHeadlessFirefox() {
ensureFirefoxBinary()
ensureFirefoxVersion()
args := []string{"--marionette"}
if !*isFFGui {
if !viper.GetBool("firefox.with-gui") {
args = append(args, "--headless")
}
if *useFFProfile != "default" {
Log("Using profile: " + *useFFProfile)
args = append(args, "-P", *useFFProfile)
profile := viper.GetString("firefox.profile")
if profile != "default" {
Log("Using profile: " + profile)
args = append(args, "-P", profile)
} else {
profilePath := getConfigFolder()
profilePath := getFirefoxProfilePath()
Log("Using default profile at: " + profilePath)
args = append(args, "--profile", profilePath)
}
firefoxProcess := exec.Command(*firefoxBinary, args...)
firefoxProcess := exec.Command(viper.GetString("firefox.path"), args...)
defer firefoxProcess.Process.Kill()
stdout, err := firefoxProcess.StdoutPipe()
if err != nil {
@ -96,18 +98,19 @@ func checkIfFirefoxIsAlreadyRunning() {
}
func ensureFirefoxBinary() {
if *firefoxBinary == "firefox" {
path := viper.GetString("firefox.path")
if path == "firefox" {
switch runtime.GOOS {
case "windows":
*firefoxBinary = getFirefoxPath()
path = getFirefoxPath()
case "darwin":
*firefoxBinary = "/Applications/Firefox.app/Contents/MacOS/firefox"
path = "/Applications/Firefox.app/Contents/MacOS/firefox"
default:
*firefoxBinary = getFirefoxPath()
path = getFirefoxPath()
}
}
if _, err := os.Stat(*firefoxBinary); os.IsNotExist(err) {
Shutdown(errors.New("Firefox binary not found: " + *firefoxBinary))
if _, err := os.Stat(path); os.IsNotExist(err) {
Shutdown(errors.New("Firefox binary not found: " + path))
}
}
@ -292,7 +295,7 @@ func setupFirefox() {
}
func startFirefox() {
if !*isUseExistingFirefox {
if !viper.GetBool("firefox.use-existing") {
writeString(0, 16, "Waiting for Firefox to connect...", tcell.StyleDefault)
if IsTesting {
writeString(0, 17, "TEST MODE", tcell.StyleDefault)

View file

@ -7,6 +7,7 @@ import (
"strings"
"github.com/go-errors/errors"
"github.com/spf13/viper"
)
func getFirefoxPath() string {
@ -17,7 +18,7 @@ func ensureFirefoxVersion() {
if runtime.GOOS == "windows" {
return
}
output := Shell(*firefoxBinary + " --version")
output := Shell(viper.GetString("firefox.path") + " --version")
pieces := strings.Split(output, " ")
version := pieces[len(pieces)-1]
if versionOrdinal(version) < versionOrdinal("57") {

View file

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/spf13/viper"
"github.com/NYTimes/gziphandler"
"github.com/ulule/limiter"
"github.com/ulule/limiter/drivers/middleware/stdlib"
@ -31,11 +32,13 @@ func HTTPServerStart() {
startFirefox()
go startWebSocketServer()
Log("Starting Browsh HTTP server")
bind := viper.GetString("http-server.bind")
port := viper.GetString("http-server.port")
serverMux := http.NewServeMux()
uncompressed := http.HandlerFunc(handleHTTPServerRequest)
limiterMiddleware := setupRateLimiter()
serverMux.Handle("/", limiterMiddleware.Handler(gziphandler.GzipHandler(uncompressed)))
if err := http.ListenAndServe(*httpServerBind+":"+*HTTPServerPort, &slashFix{serverMux}); err != nil {
if err := http.ListenAndServe(bind+":"+port, &slashFix{serverMux}); err != nil {
Shutdown(err)
}
}

View file

@ -7,6 +7,7 @@ import (
"github.com/gdamore/tcell"
"github.com/go-errors/errors"
"github.com/spf13/viper"
)
var (
@ -90,7 +91,7 @@ func handleUserKeyPress(ev *tcell.EventKey) {
}
func quitBrowsh() {
if !*isUseExistingFirefox {
if !viper.GetBool("firefox.use-existing") {
quitFirefox()
}
Shutdown(errors.New("normal"))

View file

@ -1,6 +1,7 @@
package browsh
import (
"github.com/spf13/viper"
"github.com/gdamore/tcell"
)
@ -26,7 +27,7 @@ func renderUI() {
// the browser that must be done through the webextension.
func writeString(x, y int, str string, style tcell.Style) {
xOriginal := x
if *IsHTTPServer {
if viper.GetBool("http-server-mode") {
Log(str)
return
}

View file

@ -8,6 +8,7 @@ import (
ginkgo "github.com/onsi/ginkgo"
"github.com/spf13/viper"
"browsh/interfacer/src/browsh"
)
@ -22,12 +23,12 @@ func startStaticFileServer() {
func startBrowsh() {
browsh.IsTesting = true
*browsh.IsHTTPServer = true
viper.Set("http-server-mode", true)
browsh.HTTPServerStart()
}
func getPath(path string, mode string) string {
browshServiceBase := "http://localhost:" + *browsh.HTTPServerPort
browshServiceBase := "http://localhost:" + viper.GetString("http-server.port")
staticFileServerBase := "http://localhost:" + staticFileServerPort
fullBase := browshServiceBase + "/" + staticFileServerBase
client := &http.Client{}