Compare commits

...

67 Commits

Author SHA1 Message Date
KemoNine f2292f3099 Fixup unused vars/imports
continuous-integration/drone/tag Build is passing Details
2020-09-20 20:36:57 -04:00
KemoNine d335e1f85a Fixup groupings in toml parsing ; update example config
continuous-integration/drone/tag Build is failing Details
2020-09-20 20:33:07 -04:00
KemoNine 39451ec3b3 Remove debugging output as it's not part of production releases 2020-09-20 20:20:42 -04:00
KemoNine 35e0032826 Update vendoring 2020-09-20 20:00:07 -04:00
KemoNine 0c9bfd6a7c Hard code file mode on disk write to 0644 which is what we would expect 2020-09-20 19:28:48 -04:00
KemoNine 0579b3e97d Merge branch 'master' of https://git.kemonine.info/PiFrame/piframe-go 2020-09-20 18:10:58 -04:00
KemoNine df886ff66d Update to make non existing config a soft error 2020-09-20 18:10:14 -04:00
kemonine 0c2c136413 Fix build
continuous-integration/drone/tag Build is passing Details
2020-09-19 18:12:57 -04:00
kemonine ab925c4b2c Remove arch check from drone now that multi-arch builds are enabled
continuous-integration/drone/tag Build is failing Details
2020-09-19 18:05:35 -04:00
kemonine 2ffda69ca8 Enable cross platform builds / be more specific about output arch from drone
continuous-integration/drone/tag Build was killed Details
2020-09-19 17:57:32 -04:00
KemoNine d5a06b79db Update changelog for next release ; cleanup broken releases
continuous-integration/drone/tag Build is passing Details
2020-09-06 04:14:58 -04:00
KemoNine be99f9ae41 Fix GUI build 2020-09-06 04:14:44 -04:00
KemoNine 4b0d611715 Add changelog entry for hdmi command
continuous-integration/drone/tag Build is failing Details
2020-09-06 04:12:04 -04:00
KemoNine 97a3474bd2 Implement hdmi on/off command for boot 2020-09-06 04:11:19 -04:00
KemoNine 9349b91819 Update changelog for latest release
continuous-integration/drone/tag Build is failing Details
2020-09-06 03:37:18 -04:00
KemoNine 13d019cf62 Add config watch to all tools so they are restarted if the config changes 2020-09-06 03:36:35 -04:00
KemoNine c2a21eabd9 Update changlog with latest details
continuous-integration/drone/tag Build is passing Details
2020-09-05 21:28:38 -04:00
KemoNine 20fab2ade0 Update readme eample to NOT have broken value 2020-09-05 21:28:01 -04:00
KemoNine 1ccbf4ee03 Update / adjust config handling code so its more reliable and less prone to crashes ; tweak default values + help text to ensure folks know its only seconds/minutes/hours ; add fim restart handling 2020-09-05 21:27:33 -04:00
KemoNine e979318132 Switch fim to seeded random since it uses the time since the epoch which is safe to call even on a schedule 2020-09-05 17:50:33 -04:00
KemoNine 98d5fdce2f Add support for restart interval to main slideshow 2020-09-05 17:49:55 -04:00
KemoNine 588145b163 Update fan daemon to re-evaluate the config values if there aren't valid values for temps (ie all 0's) which causes the program to not function correct, see code comments for further details 2020-09-05 16:51:00 -04:00
KemoNine 6634e7d8ef Minor tweak 2020-09-05 16:33:52 -04:00
KemoNine f8b4031aa3 Update inotify to use config file
continuous-integration/drone/tag Build is passing Details
2020-09-05 16:04:43 -04:00
KemoNine be7c3e63a2 Fixup map access in fan util
continuous-integration/drone/tag Build is passing Details
2020-09-05 15:57:08 -04:00
KemoNine 3ca4b66304 Tie fan into config (both ui and fan daemon) 2020-09-05 15:49:49 -04:00
KemoNine 5eab31f093 Cleanup fim so it can properly handle album selections ; need to play tricks due to fim NOT supporting mulitple directories via CLI
continuous-integration/drone/tag Build is passing Details
2020-09-05 02:08:57 -04:00
KemoNine 7afb3f17ee Enable saving album selections 2020-09-05 01:01:48 -04:00
KemoNine 728c281ad3 Cleanup config UI further, fix some save bugs 2020-09-05 00:09:14 -04:00
KemoNine 15dfaa31f9 Wire up more of the config ; apply hdmi on/off schedules on save 2020-09-04 23:28:01 -04:00
KemoNine 769029b9c2 Fill out GUI some more, enable saving of config file on exit 2020-09-04 20:28:32 -04:00
KemoNine e326984ca6 Update modules 2020-09-04 19:41:53 -04:00
KemoNine a33f8b7539 Add advanced menu item that tells how to edit the config via command line
continuous-integration/drone/tag Build is passing Details
2020-09-04 19:06:04 -04:00
KemoNine b0764b684a Add help text for intervals 2020-09-04 18:50:57 -04:00
KemoNine 20c73767de Add help text to hdmi on/off 2020-09-04 18:47:50 -04:00
KemoNine bfbb296784 Remove logging, finalize fan default settings 2020-09-04 18:31:39 -04:00
KemoNine 80828aa021 Tweaks for fan service to keep it offline more frequently and scale up faster ; wil adjust polling interval after further testing and verification 2020-09-04 15:20:15 -04:00
KemoNine 6c6bbe02fe Adjust looping on fan ; add debug output for fine tuning purposes 2020-09-04 15:04:44 -04:00
KemoNine 638bd93675 Add fan control service 2020-09-04 14:57:12 -04:00
KemoNine 592a152860 Updated vendoring 2020-09-04 14:56:41 -04:00
KemoNine 0e5d316731 Add temp util ; break up temp lookups into their own mini-module 2020-09-04 14:34:35 -04:00
KemoNine fffd67f8c0 Further implementation of config management 2020-09-03 21:01:13 -04:00
KemoNine 1b5d77e07a Tweak string format, exit if config changes -- let monit and / or systemd restart if the config changes on disk out from under the running app ; will add save/exit button later to ensure that making changes via the config ui doesn't cause the app to restart after every change 2020-09-03 01:25:42 -04:00
KemoNine a1c2415536 Fixup build paths for drone
continuous-integration/drone/tag Build is passing Details
2020-09-03 01:19:41 -04:00
KemoNine 30976f60fa Tweak fim kill approach so ALL sub-processes are also killed (this is really important since it's a bash script presently)
continuous-integration/drone/tag Build is failing Details
2020-09-03 01:04:08 -04:00
KemoNine 8b3827220e Add watching of config file to re-load if changed on disk while running 2020-09-03 00:48:08 -04:00
KemoNine ce9f53cb45 Add command line flag to allow only running the config ui 2020-09-03 00:24:26 -04:00
KemoNine 712f7c7a45 Add CLI flag to only show config UI and skip the slideshow display (so you can config & auto-reload the main piframe ui over ssh and similar 2020-09-02 23:56:25 -04:00
KemoNine e2bce8d442 Vendoring for command line flags 2020-09-02 23:50:36 -04:00
KemoNine 8737f2fe1a Basic config file load 2020-09-02 23:47:07 -04:00
KemoNine 0834f3779f Update vendoring 2020-09-02 23:46:55 -04:00
KemoNine 1841b19211 Split up GUI code some and modularize it 2020-09-02 20:40:28 -04:00
KemoNine a506546f1e Add album listing to config ui so they can be selected / deselected 2020-09-02 02:16:06 -04:00
KemoNine 37bbc4658d Add a TON of error handling that was previously skipped (intentionally but really needs to be there) ; added basic filesystem walk of albums dir to facilitate config of which albums are used for the slideshow 2020-09-02 01:30:27 -04:00
KemoNine e3008a6d43 Add code to apply the wifi config in the main ui 2020-09-01 20:11:27 -04:00
KemoNine 48d757a6b6 Tease apart wifi config into dedicated module and use module in existing wifi config tool 2020-09-01 19:05:09 -04:00
KemoNine 83743a9f90 Tease out fim code, further implementation of config ui (INCOMPLETE) 2020-08-31 22:57:23 -04:00
KemoNine 4a516cbed8 Further implementation of UI 2020-08-30 19:46:17 -04:00
KemoNine 494ac4a1e3 Update vendoring 2020-08-30 19:46:07 -04:00
KemoNine 55459e42f0 Updated vendoring 2020-08-30 17:57:15 -04:00
KemoNine d10c9e7a65 Add better keyboard handling to UI for fim controls and getting to the UI 2020-08-29 19:39:24 -04:00
KemoNine 38817ae4d3 Start reworking keyboard input and no longer trap signals for controls 2020-08-29 19:12:28 -04:00
KemoNine 8b0c907c3d Vendoring updates 2020-08-29 19:11:30 -04:00
KemoNine 16f7ddcd58 Fixup which service is restarted when pictures change
continuous-integration/drone/tag Build is passing Details
2020-08-28 21:21:04 -04:00
KemoNine 5212949fb0 Start building out robust UI code ; this update moves the fim slideshow advancing into the go code and keeps fim OFF stdin/out so keyboard input shouldn't affect it
continuous-integration/drone/tag Build is passing Details
2020-08-28 21:18:01 -04:00
KemoNine 255e647c5e Cleanup error handling
continuous-integration/drone/tag Build is failing Details
2020-08-28 20:02:38 -04:00
KemoNine 75a26c064d Update slideshow path to match docs
continuous-integration/drone/tag Build is passing Details
2020-08-28 18:40:33 -04:00
236 changed files with 41648 additions and 8219 deletions

View File

@ -7,10 +7,6 @@ trigger:
event:
- tag
platform:
os: linux
arch: arm64
volumes:
- name: cache
temp: {}
@ -27,9 +23,11 @@ steps:
path: /drone/src/out
commands:
- cd /drone/src
- go build -o out/wifi cmd/wifi/wifi.go
- go build -o out/inotify cmd/inotify/inotify.go
- go build -o out/ui cmd/ui/ui.go
- env GOOS=linux GOARCH=arm64 go build -o out/wifi cmd/wifi/wifi.go
- env GOOS=linux GOARCH=arm64 go build -o out/inotify cmd/inotify/inotify.go
- env GOOS=linux GOARCH=arm64 go build -o out/gui cmd/gui/gui.go
- env GOOS=linux GOARCH=arm64 go build -o out/fan cmd/fan/fan.go
- env GOOS=linux GOARCH=arm64 go build -o out/hdmi cmd/hdmi/hdmi.go
- cp CHANGELOG.md out/
- name: gitea-release
image: plugins/gitea-release

View File

@ -1,5 +1,30 @@
# Change Log
## 20200906-1
- Build bug fixes
- Add HDMI on/off command for help with ensuring screen is on OR off at boot since systemd timers won't re-run on boot
- Update tools to watch for config changes and restart if config has changed
## 20200905-4
- Update fan daemon to be better about loading config
- Update slideshow to keep it running UNTIL a user presses a key to enter the config ui
- Added support for slideshow restart interval (this is for re-randomizing the slideshow)
- Adjust randomization flag for fim to be a little more reliable (put back the seeded version)
- Adjusted config defaults and handling to be more reliable
## 20200905-3
- Update inotify to use configured path for albums instead of being hard coded
## 20200827-1 through 20200905-2
- Full bring up of main config UI
- Additional 'advanced' configuration available via config file
- Fan management daemon
- inotify dameon for watching the main photos storage for changes and restarting slideshow as appropriate
## 20200826-1
- Initial bring up of the wifi configuration utility

3
cmd/fan/README.md Normal file
View File

@ -0,0 +1,3 @@
# Fan
A simple utility to control the [Argon Fan](https://www.argon40.com/argon-fan-hat-for-raspberry-pi-4-raspberry-pi-3b-and-raspberry-pi-3-b.html) speed based on CPU/GPU temperatures.

117
cmd/fan/fan.go Normal file
View File

@ -0,0 +1,117 @@
package main
import (
"log"
"os"
"os/signal"
"syscall"
"time"
argonFan "git.sungo.io/sungo/argon/fan"
"git.kemonine.info/PiFrame/config"
"git.kemonine.info/PiFrame/utils"
)
const (
BUS = 1
ADDRESS = 0x1a
)
func main() {
// Load the config file
pfConfig, configFileProvider := config.LoadConfig(false)
// Watch for config changes and re-load config if needed
configFileProvider.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("Error setting up watch of config : %s", err)
return
}
// Bail on slideshow if there is a config change so it restarts with updated config
log.Fatalf("Config file changed! Exiting!")
})
// Get the various fan related config options as local variables
POLL_INTERVAL := pfConfig.String(config.CONFIG_KEY_FAN_POLL_INTERVAL)
SPEED_FULL_TEMP := pfConfig.Float64(config.CONFIG_KEY_FAN_SPEEDS + "." + config.CONFIG_MAP_KEY_FAN_SPEED_100)
SPEED_SEVENTY_FIVE_PERCENT_TEMP := pfConfig.Float64(config.CONFIG_KEY_FAN_SPEEDS + "." + config.CONFIG_MAP_KEY_FAN_SPEED_75)
SPEED_FIFTY_PERCENT_TEMP := pfConfig.Float64(config.CONFIG_KEY_FAN_SPEEDS + "." + config.CONFIG_MAP_KEY_FAN_SPEED_50)
SPEED_TWENTY_FIVE_PERCENT_TEMP := pfConfig.Float64(config.CONFIG_KEY_FAN_SPEEDS + "." + config.CONFIG_MAP_KEY_FAN_SPEED_25)
SPEED_MINIMUM := pfConfig.Int(config.CONFIG_KEY_FAN_MIN_SPEED)
// Setup fan and bail if we can't see it
fan, err := argonFan.New(ADDRESS, BUS)
if err != nil {
log.Fatalf("Error working with fan : %s", err)
}
// Safe exit
defer fan.SafeClose()
sigc := make(chan os.Signal, 1)
signal.Notify(
sigc,
syscall.SIGHUP,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
go func() {
<-sigc
fan.SafeClose()
os.Exit(1)
}()
// Control fan speed based on temps via a ticker / timeout
pollInterval, err := time.ParseDuration(POLL_INTERVAL)
if err != nil {
log.Fatalf("Error parsing interval duration : %s", err)
}
ticker := time.NewTicker(pollInterval)
for range ticker.C {
cpuTemp := utils.GetCPUTemp()
gpuTemp := utils.GetGPUTemp()
if cpuTemp >= SPEED_FULL_TEMP || gpuTemp >= SPEED_FULL_TEMP {
if SPEED_MINIMUM > 100 {
fan.SetSpeed(SPEED_MINIMUM)
} else {
fan.SetSpeed(100)
}
continue
}
if cpuTemp >= SPEED_SEVENTY_FIVE_PERCENT_TEMP || gpuTemp >= SPEED_SEVENTY_FIVE_PERCENT_TEMP {
if SPEED_MINIMUM > 75 {
fan.SetSpeed(SPEED_MINIMUM)
} else {
fan.SetSpeed(75)
}
continue
}
if cpuTemp >= SPEED_FIFTY_PERCENT_TEMP || gpuTemp >= SPEED_FIFTY_PERCENT_TEMP {
if SPEED_MINIMUM > 50 {
fan.SetSpeed(SPEED_MINIMUM)
} else {
fan.SetSpeed(50)
}
continue
}
if cpuTemp >= SPEED_TWENTY_FIVE_PERCENT_TEMP || gpuTemp >= SPEED_TWENTY_FIVE_PERCENT_TEMP {
if SPEED_MINIMUM > 25 {
fan.SetSpeed(SPEED_MINIMUM)
} else {
fan.SetSpeed(25)
}
continue
}
if cpuTemp < SPEED_TWENTY_FIVE_PERCENT_TEMP || gpuTemp < SPEED_TWENTY_FIVE_PERCENT_TEMP {
fan.SetSpeed(SPEED_MINIMUM)
continue
}
// We should never get here but...
// Maxing fan to be on the safe side
log.Print("pf-fan : This should never happen")
fan.SetSpeed(100)
}
}

38
cmd/gui/README.md Normal file
View File

@ -0,0 +1,38 @@
# gui
This is the main source code for the custom UI used by PiFrame. This is responsible for things like restart, WiFi config, ensuring the slide show is running and more.
## Config
The GUI uses a config similar to the following. To generate a default config, launch the gui and immediatly select the ```Save & Exit``` button. The generated configuration will be at ```/etc/default/pf.toml```.
**Please Note**: The below is a sample and may or may not match the coded defaults. We also do *not* recommend changing values that are not present in the UI unless you are sure of the consequences.
```
config-ui-only = false
[albums]
root = "/tank/pictures"
selected = ["/", "/KemoNine"]
[fan]
minspeed = 10.0
pollinginterval = "30s"
[fan.speeds]
100 = 55.0
25 = 45.0
50 = 50.0
75 = 52.0
[hdmi]
off = "*-*-* 00:00:00"
on = "*-*-* 06:00:00"
[slideshow]
restartinterval = "168h"
slideinterval = "300s"
```

67
cmd/gui/gui.go Normal file
View File

@ -0,0 +1,67 @@
package main
import (
"fmt"
"log"
"os"
"time"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/posflag"
flag "github.com/spf13/pflag"
"git.kemonine.info/PiFrame/config"
"git.kemonine.info/PiFrame/ui"
)
func main() {
// Command line flag handler
f := flag.NewFlagSet("piframe", flag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
// Command line flags
f.Bool(config.CLI_FLAG_CONFIG_ONLY, false, "Only show the config UI, NOT the slideshow")
cliFlag := f.Lookup(config.CLI_FLAG_CONFIG_ONLY)
if cliFlag != nil {
cliFlag.NoOptDefVal = "true"
}
// Process command line flags into handler
f.Parse(os.Args[1:])
// Load the config file
pfConfig, configFileProvider := config.LoadConfig(false)
// Watch for config changes and re-load config if needed
configFileProvider.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("Error setting up watch of config : %s", err)
return
}
// Give the config UI a chance to save and exit clean
time.Sleep(time.Minute)
// Bail on slideshow if there is a config change so it restarts with updated config
log.Fatalf("Config file changed! Exiting!")
})
// Process command line flags
if err := pfConfig.Load(posflag.Provider(f, ".", pfConfig), nil); err != nil {
log.Fatalf("Error loading command line flags : %s", err)
}
hideSlideshow := pfConfig.Bool(config.CLI_FLAG_CONFIG_ONLY)
// Reset the CLI flag so it's never writted to the config as 'true'
pfConfig.Load(confmap.Provider(map[string]interface{}{
config.CLI_FLAG_CONFIG_ONLY: false,
}, "."), nil)
if !hideSlideshow {
ui.Slideshow(pfConfig)
}
ui.ConfigGui(pfConfig)
}

3
cmd/hdmi/README.md Normal file
View File

@ -0,0 +1,3 @@
# hdmi
This is the source for the screen on/off tool. systemd timers are great but if the system auto-reboots overnight for update purposes the screen will remain on post-boot. This tool will ensure the screen turns back off if a PiFrame is rebooted during off hours.

81
cmd/hdmi/hdmi.go Normal file
View File

@ -0,0 +1,81 @@
package main
import (
"log"
"os/exec"
"strconv"
"strings"
"time"
"git.kemonine.info/PiFrame/config"
)
const (
CMD_VCGENCMD = "/opt/vc/bin//vcgencmd"
CMD_VCGENCMD_DISPLAY_POWER = "display_power"
)
func main() {
// Load the config file
pfConfig, _ := config.LoadConfig(false)
// Read config values
hdmiOn := pfConfig.String(config.CONFIG_KEY_HDMI_ON)
hdmiOff := pfConfig.String(config.CONFIG_KEY_HDMI_OFF)
// Strip off systemd stuff we don't care about for this purpose
hdmiOn = strings.TrimLeft(hdmiOn, "*-*-* ")
hdmiOnSplit := strings.Split(hdmiOn, ":")
// Split the config into hours/minutes/seconds
hdmiOff = strings.TrimLeft(hdmiOff, "*-*-* ")
hdmiOffSplit := strings.Split(hdmiOff, ":")
// Parse hdmi on hours/minutes/seconds into ints so they can be used to create real date/time
hdmiOnHour, err := strconv.Atoi(hdmiOnSplit[0])
if err != nil {
log.Fatalf("Could not parse hdmi on hour : %s", err)
}
hdmiOnMinute, err := strconv.Atoi(hdmiOnSplit[1])
if err != nil {
log.Fatalf("Could not parse hdmi on minute : %s", err)
}
hdmiOnSecond, err := strconv.Atoi(hdmiOnSplit[2])
if err != nil {
log.Fatalf("Could not parse htmi on second : %s", err)
}
// Parse hdmi off hours/minutes/seconds into ints so they can be used to create real date/time
hdmiOffHour, err := strconv.Atoi(hdmiOffSplit[0])
if err != nil {
log.Fatalf("Could not parse hdmi off hour : %s", err)
}
hdmiOffMinute, err := strconv.Atoi(hdmiOffSplit[1])
if err != nil {
log.Fatalf("Could not parse hdmi off minute : %s", err)
}
hdmiOffSecond, err := strconv.Atoi(hdmiOffSplit[2])
if err != nil {
log.Fatalf("Could not parse hdmi off second : %s", err)
}
// Setup date/time vars for comparison
currentTime := time.Now()
hdmiOnTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), hdmiOnHour, hdmiOnMinute, hdmiOnSecond, 0, currentTime.Location())
hdmiOffTime := time.Date(currentTime.Year(), currentTime.Month(), currentTime.Day(), hdmiOffHour, hdmiOffMinute, hdmiOffSecond, 0, currentTime.Location())
// Turn on/off screen depending on current time
vcgencmdOnOffFlag := "-1"
if currentTime.After(hdmiOnTime) && currentTime.Before(hdmiOffTime) {
vcgencmdOnOffFlag = "1"
}
if currentTime.After(hdmiOffTime) && currentTime.Before(hdmiOnTime) {
vcgencmdOnOffFlag = "0"
}
hdmiControl := exec.Command(CMD_VCGENCMD, CMD_VCGENCMD_DISPLAY_POWER, vcgencmdOnOffFlag)
err = hdmiControl.Run()
if err != nil {
log.Fatalf("Error turning display on/off (%s) : %s", vcgencmdOnOffFlag, err)
}
}

View File

@ -7,19 +7,35 @@ import (
"github.com/dietsche/rfsnotify"
"git.kemonine.info/PiFrame/config"
"git.kemonine.info/PiFrame/watchdog"
)
const (
CMD_SYSTEMCTL = "/usr/bin/systemctl"
PATH_PICTURES = "/tank/pictures"
TIMEOUT = 5 * time.Second
)
func main() {
// Create watchdog timer that restarts fim.service on timeout
// Load the config file
pfConfig, configFileProvider := config.LoadConfig(false)
// Watch for config changes and re-load config if needed
configFileProvider.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("Error setting up watch of config : %s", err)
return
}
// Bail on slideshow if there is a config change so it restarts with updated config
log.Fatalf("Config file changed! Exiting!")
})
PATH_PICTURES := pfConfig.String(config.CONFIG_KEY_ALBUMS_ROOT)
// Create watchdog timer that restarts pf-ui.service on timeout
watchdog := watchdog.New(TIMEOUT, func() {
err := exec.Command(CMD_SYSTEMCTL, "restart", "fim.service").Run()
err := exec.Command(CMD_SYSTEMCTL, "restart", "pf-ui.service").Run()
if err != nil {
log.Fatalf("Error running %s : %s", CMD_SYSTEMCTL, err)
}
@ -40,12 +56,10 @@ func main() {
go func() {
for {
select {
case event, ok := <-watcher.Events:
case _, ok := <-watcher.Events:
if !ok {
return
}
// Print out event (this is where logic will go eventually))
log.Printf("event: %#v\n", event)
// [Re]Start timer to restart slideshow after the fs events die down
watchdog.Kick()
case err, ok := <-watcher.Errors:

View File

@ -1,3 +0,0 @@
# ui
This is the main source code for the custom UI used by PiFrame. This is responsible for things like restart, WiFi config, ensuring the slide show is running and more.

View File

@ -1,58 +0,0 @@
package main
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
"github.com/gdamore/tcell"
"github.com/rivo/tview"
)
func main() {
// fim placeholder so we can operate on it when a signal is received
var fim *exec.Cmd = nil
// Setup signal listening
sigs := make(chan os.Signal)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
defer func() { close(sigs) }()
// Goroutine to handle os signals (nuke fim so we can get to config ui)
go func() {
for sig := range sigs {
fmt.Println(sig)
if fim != nil { // Just in case someone lays on ctrl-c or similar during startup
if err := fim.Process.Kill(); err != nil {
log.Fatal("failed to kill process: ", err)
}
}
}
}()
// Forever run slideshow / show ui as the main program executed on a PiFrame
for {
// Run slideshow
fim = exec.Command("/usr/local/bin/fim-slideshow.sh")
if err := fim.Run(); err != nil {
// Unwrap the error a bit so we can find out if a signal killed fim or something else
// An exit code of -1 means the program didn't exit in time or was terminated by a signal (per the docs)
if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() != -1 {
log.Fatalf("Error : ", err)
}
}
// Run config UI when slideshow stops
app := tview.NewApplication()
frame := tview.NewFrame(tview.NewBox().SetBackgroundColor(tcell.ColorBlack)).
SetBorders(2, 2, 2, 2, 4, 4).
AddText("PiFrame", true, tview.AlignCenter, tcell.ColorWhite).
AddText("Configuration Utility", true, tview.AlignCenter, tcell.ColorRed)
if err := app.SetRoot(frame, true).EnableMouse(true).Run(); err != nil {
log.Fatal(err)
}
}
}

View File

@ -6,6 +6,8 @@ import (
"os"
"os/exec"
"strings"
"git.kemonine.info/PiFrame/wifi"
)
// Various commands that we need for this process
@ -13,7 +15,6 @@ const (
CMD_BLKID = "/usr/sbin/blkid"
CMD_FINDMNT = "/usr/bin/findmnt"
CMD_MOUNT = "/usr/bin/mount"
CMD_NMCLI = "/usr/bin/nmcli"
CMD_UMOUNT = "/usr/bin/umount"
)
@ -89,30 +90,11 @@ func main() {
// Doing stuff with the file contents goes here
essid := config[0]
password := config[1]
log.Printf("Using config: %s / %s for WiFi\n", essid, password)
// Cleanup existing WiFi connections
nmcliOut, err := exec.Command(CMD_NMCLI, "-t", "connection", "show").Output()
if err != nil {
log.Fatalf("Error running %s : %s", CMD_NMCLI, err)
}
connections := strings.Split(strings.Trim(string(nmcliOut), "\n"), "\n")
for _, connection := range connections {
details := strings.Split(connection, ":")
if details[2] != "802-11-wireless" {
continue
}
log.Printf("Cleaning up WiFi connection %s", details[0])
err := exec.Command(CMD_NMCLI, "connection", "del", details[1]).Run()
if err != nil {
log.Fatalf("Error running %s : %s", CMD_NMCLI, err)
}
}
// Create new WiFi connection with network manager
log.Printf("Connecting to %s with password %s\n", essid, password)
err = exec.Command(CMD_NMCLI, "d", "wifi", "connect", essid, "password", password).Run()
if err != nil {
log.Fatalf("Error running %s : %s", CMD_NMCLI, err)
}
// Cleanup old wifi configs and apply new one
nmWifi := wifi.New(essid, password)
nmWifi.ApplyConfig()
// Unmount the filesystem and continue
err = exec.Command(CMD_UMOUNT, MOUNTPOINT).Run()
if err != nil {

3
config/README.md Normal file
View File

@ -0,0 +1,3 @@
# config
Mini module for working with PiFrame configuration file.

40
config/config.go Normal file
View File

@ -0,0 +1,40 @@
package config
import (
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
kfile "github.com/knadh/koanf/providers/file"
)
func LoadConfig(errFatalOnMissing bool) (*koanf.Koanf, *kfile.File) {
// Main config variable
var pfConfig = koanf.New(".")
// Setup defaults
pfConfig.Load(confmap.Provider(map[string]interface{}{
CONFIG_KEY_SLIDESHOW_INTERVAL: DEFAULT_SLIDESHOW_INTERVAL,
CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL: DEFAULT_SLIDESHOW_RESTART_INTERVAL,
CONFIG_KEY_HDMI_OFF: DEFAULT_HDMI_OFF,
CONFIG_KEY_HDMI_ON: DEFAULT_HDMI_ON,
CONFIG_KEY_ALBUMS_ROOT: DEFAULT_ALBUMS_ROOT,
CONFIG_KEY_ALBUMS_SELECTED: DEFAULT_ALBUM_SELECTED,
CONFIG_KEY_FAN_POLL_INTERVAL: DEFAULT_FAN_POLL_INTERVAL,
CONFIG_KEY_FAN_SPEEDS: DEFAULT_FAN_SPEEDS,
CONFIG_KEY_FAN_MIN_SPEED: DEFAULT_FAN_MIN_SPEED,
}, "."), nil)
// Bring in /etc/defaults/pf.toml if it exists
configFileProvider := kfile.Provider(CONFIG_FILE_PATH)
if err := pfConfig.Load(configFileProvider, toml.Parser()); err != nil {
if errFatalOnMissing {
log.Fatalf("Error loading config : %s", err)
} else {
log.Printf("Error loading config : %s", err)
}
}
return pfConfig, configFileProvider
}

43
config/constants.go Normal file
View File

@ -0,0 +1,43 @@
package config
const (
CLI_FLAG_CONFIG_ONLY = "config-ui-only"
)
const (
CONFIG_FILE_PATH = "/etc/default/pf.toml"
CONFIG_FILE_PATH_TMP = "/etc/default/pf.toml.new"
CONFIG_KEY_SLIDESHOW_INTERVAL = "slideshow.slideinterval"
CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL = "slideshow.restartinterval"
CONFIG_KEY_HDMI_OFF = "hdmi.off"
CONFIG_KEY_HDMI_ON = "hdmi.on"
CONFIG_KEY_ALBUMS_ROOT = "albums.root"
CONFIG_KEY_ALBUMS_SELECTED = "albums.selected"
CONFIG_KEY_FAN_POLL_INTERVAL = "fan.pollinginterval"
CONFIG_KEY_FAN_SPEEDS = "fan.speeds"
CONFIG_KEY_FAN_MIN_SPEED = "fan.minspeed"
CONFIG_MAP_KEY_FAN_SPEED_25 = "25"
CONFIG_MAP_KEY_FAN_SPEED_50 = "50"
CONFIG_MAP_KEY_FAN_SPEED_75 = "75"
CONFIG_MAP_KEY_FAN_SPEED_100 = "100"
)
const (
DEFAULT_SLIDESHOW_INTERVAL = "300s"
DEFAULT_SLIDESHOW_RESTART_INTERVAL = "168h"
DEFAULT_HDMI_OFF = "*-*-* 00:00:00"
DEFAULT_HDMI_ON = "*-*-* 06:00:00"
DEFAULT_ALBUMS_ROOT = "/tank/pictures"
DEFAULT_FAN_POLL_INTERVAL = "30s"
DEFAULT_FAN_MIN_SPEED = 10
)
var DEFAULT_ALBUM_SELECTED = []string{"/"}
// Speed : Temp to activate speed
var DEFAULT_FAN_SPEEDS = map[string]float64{
CONFIG_MAP_KEY_FAN_SPEED_25: 45.00,
CONFIG_MAP_KEY_FAN_SPEED_50: 50.00,
CONFIG_MAP_KEY_FAN_SPEED_75: 52.00,
CONFIG_MAP_KEY_FAN_SPEED_100: 55.00,
}

16
go.mod
View File

@ -3,13 +3,17 @@ module git.kemonine.info/PiFrame
go 1.15
require (
github.com/d2r2/go-bh1750 v0.0.0-20181222061755-1195122364ab
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e
github.com/davecgh/go-spew v1.1.1 // indirect
git.sungo.io/sungo/argon v0.0.0-20200904233623-18a98e1a4706
github.com/dietsche/rfsnotify v0.0.0-20200716145600-b37be6e4177f
github.com/fsnotify/fsnotify v1.4.9 // indirect
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
github.com/gdamore/tcell v1.4.0
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9
github.com/guillermo/go.procmeminfo v0.0.0-20131127224636-be4355a9fb0e
github.com/knadh/koanf v0.13.0
github.com/mitchellh/mapstructure v1.3.3 // indirect
github.com/pelletier/go-toml v1.8.1 // indirect
github.com/rivo/tview v0.0.0-20200915114512-42866ecf6ca6
github.com/spf13/pflag v1.0.5
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff // indirect
golang.org/x/text v0.3.3 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
)

212
go.sum
View File

@ -1,14 +1,39 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
git.sungo.io/sungo/argon v0.0.0-20200904233623-18a98e1a4706 h1:gD/4qfTLeuaG9/MqcQYzoVutiADl6JtYL05PcPfETwc=
git.sungo.io/sungo/argon v0.0.0-20200904233623-18a98e1a4706/go.mod h1:3XJJhLC7vO4Yk96G/LEiiwBDXAEdj2lVB/oueweUShY=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
github.com/d2r2/go-bh1750 v0.0.0-20181222061755-1195122364ab h1:8zeQTn3owfeyIA5KIhtvcj9wlMStdFaxbYwPkWhGVgo=
github.com/d2r2/go-bh1750 v0.0.0-20181222061755-1195122364ab/go.mod h1:3atw7ac57A1fKlJIGeLBaeXdh80Revqk6uVl7bZyH10=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc h1:HLRSIWzUGMLCq4ldt0W1GLs3nnAxa5EGoP+9qHgh6j0=
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc/go.mod h1:AwxDPnsgIpy47jbGXZHA9Rv7pDkOJvQbezPuK1Y+nNk=
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e h1:ZG3JBA6rPRl0xxQ+nNSfO7tor8w+CNCTs05DNJQYbLM=
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e/go.mod h1:oA+9PUt8F1aKJ6o4YU1T120i7sgo1T6/1LWEEBy0BSs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dietsche/rfsnotify v0.0.0-20200716145600-b37be6e4177f h1:b3QvpXLSx1U13VM79rSkA+6Xv4lmT/urEMzA36Yma0U=
github.com/dietsche/rfsnotify v0.0.0-20200716145600-b37be6e4177f/go.mod h1:ztitxkMUaBsHRey1tS5xFCd4gm/zAQwA9yfCP5y4cAA=
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807 h1:jdjd5e68T4R/j4PWxfZqcKY8KtT9oo8IPNVuV4bSXDQ=
github.com/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807/go.mod h1:Xoiu5VdKMvbRgHuY7+z64lhu/7lvax/22nzASF6GrO8=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
@ -16,25 +41,208 @@ github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
github.com/gdamore/tcell v1.4.0 h1:vUnHwJRvcPQa3tzi+0QI4U9JINXYJlOz9yiaiPQ2wMU=
github.com/gdamore/tcell v1.4.0/go.mod h1:vxEiSDZdW3L+Uhjii9c3375IlDmR05bzxY404ZVSMo0=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.11.2/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/guillermo/go.procmeminfo v0.0.0-20131127224636-be4355a9fb0e h1:/6/OurM62Ddm8CR8PveE0a+ql2mL+ycAhOwd563kpdg=
github.com/guillermo/go.procmeminfo v0.0.0-20131127224636-be4355a9fb0e/go.mod h1:TQrLAmkOSnZ4g1eFORtCfTEbFuVZD0Zm55vdnrilBaw=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/knadh/koanf v0.12.0 h1:xQo0Y43CbzOix0tTeE+plIcfs1pTuaUI1/SsvDl2ROI=
github.com/knadh/koanf v0.12.0/go.mod h1:31bzRSM7vS5Vm9LNLo7B2Re1zhLOZT6EQKeodixBikE=
github.com/knadh/koanf v0.13.0 h1:OEjNdmrP/5oAhJkNwTtarioqOC4xe6WxRK8Q5ffW8WU=
github.com/knadh/koanf v0.13.0/go.mod h1:7XDF7OJIqSQLUZnaXkjb1HB3CgMEYHyrzmgT8A6xAaE=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.3.3 h1:SzB1nHZ2Xi+17FP0zVQBHIZqvwRN9408fJO8h+eeNA8=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.8.1 h1:1Nf83orprkJyknT6h7zbuEGUEjcyVlCxSUGTENmNCRM=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/rhnvrm/simples3 v0.5.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9 h1:csnip7QsoiE2Ee0RkELN1YggwejK2EFfcjU6tXOT0Q8=
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9/go.mod h1:xV4Aw4WIX8cmhg71U7MUHBdpIQ7zSEXdRruGHLaEAOc=
github.com/rivo/tview v0.0.0-20200915114512-42866ecf6ca6 h1:LhmHZTzElCYlOXEWXWOQXy/vgjPsdiDb7LzHV8mTKvI=
github.com/rivo/tview v0.0.0-20200915114512-42866ecf6ca6/go.mod h1:xV4Aw4WIX8cmhg71U7MUHBdpIQ7zSEXdRruGHLaEAOc=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/warthog618/config v0.4.1/go.mod h1:IzcIkVay6dCubN3WBAJzPuqHyE1fTPxICvKTQ/2JA9g=
github.com/warthog618/gpio v1.0.0/go.mod h1:3yuGbOkcAcs8/pRFEnCnN7Qt2S+TkISbFXM+5gliAZM=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190927073244-c990c680b611/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff h1:1CPUrky56AcgSpxz/KfgzQWzfG09u5YOL8MvPYBlrL8=
golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20190921001708-c4c64cad1fd0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

3
ui/README.md Normal file
View File

@ -0,0 +1,3 @@
# ui
This is the main implementation for various aspects of the main PiFrame [G]UI

595
ui/config.go Normal file
View File

@ -0,0 +1,595 @@
package ui
import (
"bytes"
"fmt"
"log"
"math"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
"text/template"
"github.com/gdamore/tcell"
"github.com/guillermo/go.procmeminfo"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
"github.com/rivo/tview"
"git.kemonine.info/PiFrame/config"
"git.kemonine.info/PiFrame/utils"
"git.kemonine.info/PiFrame/wifi"
)
const (
CMD_SYSTEMCTL = "/usr/bin/systemctl"
CMD_FINDMNT = "/usr/bin/findmnt"
SYNCTHING_FOLDER_SKIP = ".stfolder"
)
const (
PAGE_MAIN_UI = "PAGE_MAIN_UI"
PAGE_SAVE_EXIT = "PAGE_SAVE_EXIT"
PAGE_EXIT = "PAGE_EXIT"
PAGE_REBOOT = "PAGE_REBOOT"
PAGE_POWEROFF = "PAGE_POWEROFF"
)
// Bullshit to remove an element from a slice
func remove(s []string, index int) []string {
return append(s[:index], s[index+1:]...)
}
// Housekeeping
var app *tview.Application
var main *tview.Flex
var menu *tview.List
// Function to reset the main input area that's used all over the place
func resetMain() {
main.Clear()
app.SetFocus(menu)
}
func ConfigGui(pfconfig *koanf.Koanf) {
// Memory info for status panel
meminfo := &procmeminfo.MemInfo{}
err := meminfo.Update()
if err != nil {
log.Fatalf("Error getting memory info : %s", err)
}
// Network interfaces for status panel
ifaces, err := net.Interfaces()
if err != nil {
log.Fatalf("Error getting netork interfaces : %s", err)
return
}
// Disk use
findmntOut, err := exec.Command(CMD_FINDMNT, "-n", "-l",
"-o", "TARGET,USE%",
"-t", "ext4,exfat,vfat,btrfs,zfs,xfs").Output()
if err != nil {
log.Fatalf("Error getting disk use : %s", err)
}
filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n")
// GPU Temp
gpuTemp := fmt.Sprintf("%.2f'C", utils.GetGPUTemp())
// CPU Temp
cpuTemp := fmt.Sprintf("%.2f'C", utils.GetCPUTemp())
// Get list of all folders that can be used as albums
var albums []string
err = filepath.Walk(pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT), func(path string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi.IsDir() {
if strings.Contains(path, SYNCTHING_FOLDER_SKIP) {
return nil
}
albumName := strings.TrimPrefix(path, pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT))
if albumName == "" {
albumName = "Main Folder"
}
albums = append(albums, albumName)
}
return nil
})
if err != nil {
log.Fatalf("Error getting list of albums : %s", err)
}
// Run config UI when slideshow stops
app = tview.NewApplication()
// Header
headerTitle := tview.NewTextView().
SetText("PiFrame").
SetTextAlign(tview.AlignCenter).
SetTextColor(tcell.ColorAqua)
headerSubTitle := tview.NewTextView().
SetText("Management Utility").
SetTextAlign(tview.AlignCenter).
SetTextColor(tcell.ColorSilver)
header := tview.NewFlex().
SetDirection(tview.FlexRow)
header.AddItem(headerTitle, 0, 1, false).
AddItem(headerSubTitle, 0, 1, false)
// Footer fields (Left Column)
saveExitButton := tview.NewButton("Save & Exit").
SetBackgroundColorActivated(tcell.ColorGray)
saveExitButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true).
SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorGreen).
SetRect(0, 0, 22, 3)
exitButton := tview.NewButton("Exit").
SetBackgroundColorActivated(tcell.ColorGray)
exitButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true).
SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorYellow).
SetRect(0, 0, 22, 3)
rebootButton := tview.NewButton("Reboot").
SetBackgroundColorActivated(tcell.ColorGray)
rebootButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true).
SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorFuchsia).
SetRect(0, 0, 22, 3)
powerOffButton := tview.NewButton("Power Off").
SetBackgroundColorActivated(tcell.ColorGray)
powerOffButton.SetLabelColor(tcell.ColorBlack).
SetBorder(true).
SetBorderColor(tcell.ColorBlack).
SetBackgroundColor(tcell.ColorRed).
SetRect(0, 0, 22, 3)
// Footer
footer := tview.NewFlex()
footer.AddItem(saveExitButton, 0, 1, false).
AddItem(exitButton, 0, 1, false).
AddItem(rebootButton, 0, 1, false).
AddItem(powerOffButton, 0, 1, false)
// Setup menu
menu = tview.NewList()
menu.SetBorder(true).
SetTitle("Menu").
SetTitleColor(tcell.ColorAqua)
menu.AddItem("Select Albums", "", '1', nil)
menu.AddItem("Intervals", "", '2', nil)
menu.AddItem("WiFi", "", '3', nil)
menu.AddItem("HDMI On/Off", "", '5', nil)
menu.AddItem("Advanced", "", '6', nil)
// Setup base var for main column so the menu setup is easier to manage
main = tview.NewFlex().
SetDirection(tview.FlexRow)
// Setup main panel (Center column)
main.SetTitle("").
SetBorder(true).
SetTitleColor(tcell.ColorAqua)
// Select Albums Form
selectAlbumsForm := tview.NewForm()
configSelectedAlbums := pfconfig.Strings(config.CONFIG_KEY_ALBUMS_SELECTED)
albumCheckboxes := []*tview.Checkbox{}
for _, album := range albums {
albumSelected := false
for _, configSelectedAlbum := range configSelectedAlbums {
if configSelectedAlbum == "/" {
configSelectedAlbum = "Main Folder"
}
if album == configSelectedAlbum {
albumSelected = true
}
}
albumCheckbox := tview.NewCheckbox()
albumCheckbox.SetLabel(album)
albumCheckbox.SetChecked(albumSelected)
albumCheckboxes = append(albumCheckboxes, albumCheckbox)
selectAlbumsForm.AddFormItem(albumCheckbox)
}
selectAlbumsForm.AddButton("Apply", resetMain)
selectAlbumsForm.AddButton("Cancel", resetMain)
// Slide Interval Form
intervalsForm := tview.NewForm()
configSlideInterval := pfconfig.String(config.CONFIG_KEY_SLIDESHOW_INTERVAL)
configRestartInterval := pfconfig.String(config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL)
intervalsForm.AddInputField("Slide", configSlideInterval, 0, nil, func(value string) {
configSlideInterval = value
})
intervalsForm.AddInputField("Restart/Reshuffle", configRestartInterval, 0, nil, func(value string) {
configRestartInterval = value
})
intervalsForm.AddButton("Apply", resetMain)
intervalsForm.AddButton("Cancel", resetMain)
// WiFi Config Form
wifiConfigForm := tview.NewForm()
wifiConfigAccessPoint := ""
wifiConfigPassword := ""
wifiConfigForm.AddInputField("Access Point", "", 0, nil, func(value string) {
wifiConfigAccessPoint = value
})
wifiConfigForm.AddPasswordField("Password", "", 0, '*', func(value string) {
wifiConfigPassword = value
})
wifiConfigForm.AddButton("Apply", func() {
// Cleanup old wifi configs and apply new one
nmWifi := wifi.New(wifiConfigAccessPoint, wifiConfigPassword)
nmWifi.ApplyConfig()
resetMain()
})
wifiConfigForm.AddButton("Cancel", resetMain)
// HDMI On/Off Form
hdmiForm := tview.NewForm()
configHDMIOff := pfconfig.String(config.CONFIG_KEY_HDMI_OFF)
configHDMIOn := pfconfig.String(config.CONFIG_KEY_HDMI_ON)
hdmiForm.AddInputField("HDMI Off Schedule", configHDMIOff, 0, nil, func(value string) {
configHDMIOff = value
})
hdmiForm.AddInputField("HDMI On Schedule", configHDMIOn, 0, nil, func(value string) {
configHDMIOn = value
})
hdmiForm.AddButton("Apply", resetMain)
hdmiForm.AddButton("Cancel", resetMain)
// Advanced config form
advancedForm := tview.NewForm()
advancedForm.AddButton("Go Back", resetMain)
// Setup menu selection handler
menu.SetSelectedFunc(func(index int, title string, desc string, shortcut rune) {
if title == "Select Albums" {
main.SetTitle("Select Albums")
main.Clear()
main.AddItem(selectAlbumsForm, 0, 1, true)
app.SetFocus(selectAlbumsForm)
}
if title == "WiFi" {
main.SetTitle("Configure WiFi")
main.Clear()
main.AddItem(wifiConfigForm, 0, 1, true)
app.SetFocus(wifiConfigForm)
}
if title == "Intervals" {
main.SetTitle("Configure Intervals")
main.Clear()
main.AddItem(intervalsForm, 0, 1, true)
main.AddItem(tview.NewTextView().SetText("Intervals are a number + letter\n\nUse\ns for seconds\nm for minutes\nh for hours"), 0, 1, false)
app.SetFocus(intervalsForm)
}
if title == "HDMI On/Off" {
main.SetTitle("Configure HDMI On/Off")
main.Clear()
main.AddItem(hdmiForm, 0, 1, true)
main.AddItem(tview.NewTextView().SetText("These values are date+time combos\n\nPlease see https://www.freedesktop.org/software/systemd/man/systemd.time.html for details\n\nONLY adjust the times (24h format) if unsure of what to change"), 0, 1, false)
app.SetFocus(hdmiForm)
}
if title == "Advanced" {
main.SetTitle("Advanced Configuration")
main.Clear()
main.AddItem(tview.NewTextView().SetText("For advanced configuration edit /etc/default/pf.toml via the command line. There are a few config values that are DANGEROUS to adjust and not exposed through this ui."), 0, 1, false)
main.AddItem(advancedForm, 0, 1, false)
app.SetFocus(advancedForm)
}
})
// Side bar fields
sideBarCPUTempTitle := tview.NewTextView().
SetText("CPU Temperature").
SetTextColor(tcell.ColorYellow)
sideBarCPUTemp := tview.NewTextView().
SetText(fmt.Sprintf(" %s", cpuTemp))
sideBarGPUTempTitle := tview.NewTextView().
SetText("GPU Temperature").
SetTextColor(tcell.ColorYellow)
sideBarGPUTemp := tview.NewTextView().
SetText(fmt.Sprintf(" %s", gpuTemp))
sideBarMemoryTitle := tview.NewTextView().
SetText("Memory Use (Mb)").
SetTextColor(tcell.ColorYellow)
divisor := math.Pow(1024.0, 2.0)
sideBarMemoryStats := tview.NewTextView().
SetText(fmt.Sprintf(" %.1f / %.1f",
float64(meminfo.Used())/divisor,
float64(meminfo.Total())/divisor))
sideBarSwapTitle := tview.NewTextView().
SetText("Swap Use").
SetTextColor(tcell.ColorYellow)
sideBarSwapStats := tview.NewTextView().
SetText(fmt.Sprintf(" %d%%", meminfo.Swap()))
sideBarFilesystemTitle := tview.NewTextView().
SetText("Disk Use").
SetTextColor(tcell.ColorYellow)
var sideBarFilesystems []*tview.TextView
for _, i := range filesystems {
filesystemAsTextView := tview.NewTextView().
SetText(fmt.Sprintf(" %s", i))
sideBarFilesystems = append(sideBarFilesystems, filesystemAsTextView)
}
sideBarIPAddressesTitle := tview.NewTextView().
SetText("IP Addresses").
SetTextColor(tcell.ColorYellow)
var sideBarIPAddresses []*tview.TextView
for _, i := range ifaces {
addrs, err := i.Addrs()
if err != nil {
continue
}
for _, a := range addrs {
ipAsTextView := tview.NewTextView().
SetText(fmt.Sprintf(" %v : %s", i.Name, a.String()))
sideBarIPAddresses = append(sideBarIPAddresses, ipAsTextView)
}
}
// Setup side bar (Right column)
sideBar := tview.NewFlex().
SetDirection(tview.FlexRow)
sideBar.SetTitle("System Info").
SetBorder(true).
SetTitleColor(tcell.ColorAqua)
sideBar.AddItem(sideBarCPUTempTitle, 1, 1, false)
sideBar.AddItem(sideBarCPUTemp, 1, 1, false)
sideBar.AddItem(sideBarGPUTempTitle, 1, 1, false)
sideBar.AddItem(sideBarGPUTemp, 1, 1, false)
sideBar.AddItem(sideBarMemoryTitle, 1, 1, false)
sideBar.AddItem(sideBarMemoryStats, 1, 1, false)
sideBar.AddItem(sideBarSwapTitle, 1, 1, false)
sideBar.AddItem(sideBarSwapStats, 1, 1, false)
sideBar.AddItem(sideBarFilesystemTitle, 1, 1, false)
for _, filesystemAsTextView := range sideBarFilesystems {
sideBar.AddItem(filesystemAsTextView, 1, 1, false)
}
sideBar.AddItem(sideBarIPAddressesTitle, 1, 1, false)
for _, ipAsTextView := range sideBarIPAddresses {
sideBar.AddItem(ipAsTextView, 1, 1, false)
}
// Pages
pages := tview.NewPages()
// Main UI
mainUI := tview.NewGrid().
SetRows(2, 0, 4).
SetColumns(25, 50, 50).
SetBorders(true).
AddItem(header, 0, 0, 1, 3, 0, 0, false).
AddItem(footer, 2, 0, 1, 3, 0, 0, false)
mainUI.AddItem(menu, 1, 0, 1, 1, 0, 100, true).
AddItem(main, 1, 1, 1, 1, 0, 100, false).
AddItem(sideBar, 1, 2, 1, 1, 0, 100, false)
pages.AddPage(PAGE_MAIN_UI, mainUI, true, true)
// Button modals
saveExitModal := tview.NewModal().
SetText("Are you sure you want to [red]SAVE [red]& [red]EXIT?").
AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" {
// Deal with selected albums
for _, checkbox := range albumCheckboxes {
checkboxLabel := checkbox.GetLabel()
// Undo beautification of checkbox label
if checkboxLabel == "Main Folder" {
checkboxLabel = "/"
}
// Track whether or not we need to add OR remove from config
selected := checkbox.IsChecked()
inConfig := false
configIndex := 0
// Walk the existing config values and bail if/when we find the album
for configIndex = range configSelectedAlbums {
if checkboxLabel == configSelectedAlbums[configIndex] {
inConfig = true
break
}
}
// We need to add to the config
if selected && !inConfig {
configSelectedAlbums = append(configSelectedAlbums, checkboxLabel)
}
// We need to remove from the config
if !selected && inConfig {
configSelectedAlbums = remove(configSelectedAlbums, configIndex)
}
}
// Apply HDMI configuration (on/off)
var hdmiForDisk bytes.Buffer
valueForTemplate := utils.SystemdTimer{OnCalendar: configHDMIOn}
screenOn, err := template.New("screenon.systemd.timer").Parse(utils.SCREEN_ON_DOT_TIMER)
if err != nil {
log.Fatalf("Error setting up screen on systemd timer : %s", err)
}
err = screenOn.Execute(&hdmiForDisk, valueForTemplate)
if err != nil {
log.Fatalf("Error setting up screen on systemd timer : %s", err)
}
utils.WriteFile(utils.SCREEN_ON_TIMER_PATH, utils.SCREEN_ON_TIMER_PATH+".tmp", hdmiForDisk.Bytes())
valueForTemplate.OnCalendar = configHDMIOff
hdmiForDisk.Truncate(0)
screenOff, err := template.New("screenoff.systemd.timer").Parse(utils.SCREEN_OFF_DOT_TIMER)
if err != nil {
log.Fatalf("Error setting up screen off systemd timer : %s", err)
}
err = screenOff.Execute(&hdmiForDisk, valueForTemplate)
if err != nil {
log.Fatalf("Error setting up screen off systemd timer : %s", err)
}
utils.WriteFile(utils.SCREEN_OFF_TIMER_PATH, utils.SCREEN_OFF_TIMER_PATH+".tmp", hdmiForDisk.Bytes())
// Reload systemd units after applying HDMI config
utils.SystemdDaemonReload()
// Apply configuration updates to main config manager prior to saving
pfconfig.Load(confmap.Provider(map[string]interface{}{
config.CONFIG_KEY_SLIDESHOW_INTERVAL: configSlideInterval,
config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL: configRestartInterval,
config.CONFIG_KEY_HDMI_ON: configHDMIOn,
config.CONFIG_KEY_HDMI_OFF: configHDMIOff,
config.CONFIG_KEY_ALBUMS_SELECTED: configSelectedAlbums,
}, "."), nil)
// Save configuration
parser := toml.Parser()
configForDisk, err := pfconfig.Marshal(parser)
if err != nil {
log.Fatalf("Error preparing config for disk write : %s", err)
}
utils.WriteFile(config.CONFIG_FILE_PATH, config.CONFIG_FILE_PATH_TMP, configForDisk)
app.Stop()
os.Exit(0)
}
pages.SwitchToPage(PAGE_MAIN_UI)
})
pages.AddPage(PAGE_SAVE_EXIT, saveExitModal, true, false)
saveExitButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_SAVE_EXIT)
})
exitModal := tview.NewModal().
SetText("Are you sure you want to [red]EXIT?").
AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" {
app.Stop()
}
pages.SwitchToPage(PAGE_MAIN_UI)
})
pages.AddPage(PAGE_EXIT, exitModal, true, false)
exitButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_EXIT)
})
rebootModal := tview.NewModal().
SetText("Are you sure you want to [red]REBOOT?").
AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" {
err := exec.Command(CMD_SYSTEMCTL, "reboot").Run()
if err != nil {
log.Fatalf("Could not reboot : %s ", err)
}
}
pages.SwitchToPage(PAGE_MAIN_UI)
})
pages.AddPage(PAGE_REBOOT, rebootModal, true, false)
rebootButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_REBOOT)
})
powerOffModal := tview.NewModal().
SetText("Are you sure you want to [red]POWER [red]OFF?").
AddButtons([]string{"Yes", "Cancel"}).
SetDoneFunc(func(buttonIndex int, buttonLabel string) {
if buttonLabel == "Yes" {
err := exec.Command(CMD_SYSTEMCTL, "poweroff").Run()
if err != nil {
log.Fatalf("Could not power off : %s ", err)
}
}
pages.SwitchToPage(PAGE_MAIN_UI)
})
pages.AddPage(PAGE_POWEROFF, powerOffModal, true, false)
powerOffButton.SetSelectedFunc(func() {
pages.ShowPage(PAGE_POWEROFF)
})
// Setup tracking of which are of the UI can/has focus
primitivesThatCanFocus := []tview.Primitive{menu, saveExitButton, exitButton, rebootButton, powerOffButton}
currentFocus := 0
// Setup basic switching between main menu and buttons for the UI
app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey {
// Don't process input if we aren't on an existing element that can focus
canContinue := false
focusedPrimitive := app.GetFocus()
// Override some of the default behavior so up/dn move between fields in forms
// Per API GetFocusedItemIndex on a form will be -1 if a form item isn't currently focused
// We use this as a bit of a cheat to figure out if we're inside of a form that needs better nav options for users (ie. tab doesn't exist on a remote)
albumField, albumButton := selectAlbumsForm.GetFocusedItemIndex()
intervalField, intervalButton := intervalsForm.GetFocusedItemIndex()
wifiField, wifiButton := wifiConfigForm.GetFocusedItemIndex()
hdmiField, hdmiButton := hdmiForm.GetFocusedItemIndex()
advancedField, advancedButton := advancedForm.GetFocusedItemIndex()
if wifiField != -1 || wifiButton != -1 ||
albumField != -1 || albumButton != -1 ||
intervalField != -1 || intervalButton != -1 ||
hdmiField != -1 || hdmiButton != -1 ||
advancedField != -1 || advancedButton != -1 {
switch event.Key() {
case tcell.KeyUp:
return tcell.NewEventKey(tcell.KeyBacktab, 0, event.Modifiers())
case tcell.KeyDown:
return tcell.NewEventKey(tcell.KeyTab, 0, event.Modifiers())
}
}
// Standard override for the screen primitives we are using (ie. don't take over control in modals)
for _, primitive := range primitivesThatCanFocus {
if primitive == focusedPrimitive {
canContinue = true
break
}
}
// Bail if we shouldn't be affecting user input
if !canContinue {
return event
}
// Add various forms of nav that make sense for a TUI (common stuff folks are used to)
currentFocusChanged := false
switch event.Key() {
case tcell.KeyRight, tcell.KeyTab:
currentFocus += 1
if currentFocus >= len(primitivesThatCanFocus) {
currentFocus = 0
}
currentFocusChanged = true
case tcell.KeyLeft, tcell.KeyBacktab:
currentFocus -= 1
if currentFocus < 0 {
currentFocus = len(primitivesThatCanFocus) - 1
}
currentFocusChanged = true
}
// Update the focus based on navigation
if currentFocusChanged {
app.SetFocus(primitivesThatCanFocus[currentFocus])
return nil
}
// Pass through event so the main UI can process stuff properly
return event
})
// Show UI and panic if there are any errors
if err := app.SetRoot(pages, true).SetFocus(primitivesThatCanFocus[currentFocus]).EnableMouse(false).Run(); err != nil {
log.Fatalf("Failed to run UI : %s", err)
}
}

266
ui/slideshow.go Normal file
View File

@ -0,0 +1,266 @@
package ui
import (
"context"
"io"
"io/ioutil"
"log"
"os"
"os/exec"
"syscall"
"time"
"github.com/eiannone/keyboard"
"github.com/knadh/koanf"
"git.kemonine.info/PiFrame/config"
)
const (
PATH_TEMP_FOR_SLIDESHOW = "/run/piframe/fim"
)
// fim placeholders so we can reset them as needed
var fim *exec.Cmd = nil
var stdin io.WriteCloser = nil
func setupFim(PATH_TEMP_FOR_SLIDESHOW string) {
// Prep slideshow command and arguments
// NOTE: The random flag is seeded with time() ; this is bad as we will be restarting the slideshow at about the same time per the configurd schedule
// We use the non-seeded form to ensure that it's a little more random (or at least hope it's a little more random)
CMD_FIM := "/usr/bin/fim"
ARGS_FIM := []string{"--no-commandline", "--no-history", "--etc-fimrc", "/usr/local/etc/fimrc",
"--device", "/dev/fb0", "--vt", "1",
"--execute-commands-early", "\"clear\"", "--final-commands", "\"clear\"",
"--autozoom", "--random", "--recursive", "--cd-and-readdir",
PATH_TEMP_FOR_SLIDESHOW}
// fim command that'll be executed
fim = exec.Command(CMD_FIM, ARGS_FIM...)
// Put fim into a process group so ALL processes that may be executed are exited when main process exits
fim.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
// Setup stdin for fim to control slideshow
stdinLocal, err := fim.StdinPipe()
if err != nil {
log.Fatalf("Error getting fim stdin : %s", err)
}
stdin = stdinLocal
}
func Slideshow(pfconfig *koanf.Koanf) {
// Prep folder setup needed for fim
// fim does NOT allow multiple folders passed on the CLI (as far as KemoNine can tell)
// We build a temp folder stup in /run/piframe/fim that has symlinks to selected albums
// After we build up this directory setup we'll kick off fim against the temp dir with recursive
// /run is a tmpfs so this won't wear on the sd card storage
// Create temp folder
_, err := os.Stat(PATH_TEMP_FOR_SLIDESHOW)
if os.IsNotExist(err) {
errDir := os.MkdirAll(PATH_TEMP_FOR_SLIDESHOW, 0755)
if errDir != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
}
// Cleanup temp folder if it already existed
dirRead, err := os.Open(PATH_TEMP_FOR_SLIDESHOW)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
dirFiles, err := dirRead.Readdir(0)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
// Loop over the directory's files.
for index := range dirFiles {
fileHere := dirFiles[index]
// Get name of file and its full path.
nameHere := fileHere.Name()
fullPath := PATH_TEMP_FOR_SLIDESHOW + "/" + nameHere
// Remove the file.
err = os.Remove(fullPath)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
}
// Setup symlinks to selected albums to be used with slideshow
// Add albums full paths to command line args for fim
albumRootPath := pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT)
for _, album := range pfconfig.Strings(config.CONFIG_KEY_ALBUMS_SELECTED) {
source := albumRootPath + album
destination := PATH_TEMP_FOR_SLIDESHOW + album
if album == "/" {
files, err := ioutil.ReadDir(albumRootPath)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
for _, file := range files {
filePath := albumRootPath + "/" + file.Name()
fileStat, err := os.Stat(filePath)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
mode := fileStat.Mode()
if mode.IsRegular() {
err = os.Symlink(filePath, PATH_TEMP_FOR_SLIDESHOW+"/"+file.Name())
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
}
}
continue
}
err = os.Symlink(source, destination)
if err != nil {
log.Fatalf("Error setting up slideshow : %s", err)
}
}
// Setup fim for run
// Commands can't be re-used so we move this to a function to support the restart interval cleanly
setupFim(PATH_TEMP_FOR_SLIDESHOW)
// Advance slideshow every interval as defined in const()
slideshowAdvanceDurationString := pfconfig.String(config.CONFIG_KEY_SLIDESHOW_INTERVAL)
slideshowAdvanceDuration, err := time.ParseDuration(slideshowAdvanceDurationString)
if err != nil {
log.Fatalf("Error parsing slide duration : %s", err)
}
ticker := time.NewTicker(slideshowAdvanceDuration)
stop_ticker := make(chan struct{})
go func() {
for {
select {
case <-ticker.C:
_, err = io.WriteString(stdin, "n")
if err != nil {
log.Fatalf("Error advancing slides : %s", err)
}
case <-stop_ticker:
ticker.Stop()
stdin.Close()
return
}
}
}()
// Start watching for key strokes and echo them back to stdout
keysEvents, err := keyboard.GetKeys(10)
if err != nil {
log.Fatalf("Error setting up keyboard listener : %s", err)
}
// NOT deferring keyboard.close as we do that by hand further down when fim exits ahead of showing the config UI
// Keep running fim until we're signaled by the user to do otherwise
// Common reasons that we MUST do this kind of goofy (bugs / behavior)
// - fim doesn't quite see the /run changes where the albums are linked and bails out
// - OOM kicks in and kills fim (it's kinda RAM heavy sometimes)
// - Corrupt or invalid image causes fim to crash
// - The timeout for re-randomizing kicked free and fim needs to restart
// - fim thinks the slideshow has 'ended' for whatever reason and we really want to always show no matter what
STOP_SLIDESHOW := false
// Goroutine for tracking which keys are pressed and controlling fim if appropriate
keyboardCtx, keyboardCancel := context.WithCancel(context.Background())
go func(keyboardCtx context.Context) {
for {
select {
case <-keyboardCtx.Done():
return
case event := <-keysEvents:
if event.Err != nil {
log.Fatalf("Error listening to key events : %s", err)
}
// Keys for fim event management (previous/next in particular)
fimKey := ""
if event.Key == keyboard.KeyArrowLeft || event.Key == keyboard.KeyArrowDown {
fimKey = "p"
}
if event.Key == keyboard.KeyArrowRight || event.Key == keyboard.KeyArrowUp {
fimKey = "n"
}
// Exit fim and move to the config UI
if event.Key == keyboard.KeyEsc || event.Key == keyboard.KeyEnter || event.Key == keyboard.KeySpace {
// We are being told to stop the slideshow, oblidge the user
STOP_SLIDESHOW = true
if fim != nil { // Just in case someone lays on exit key or similar during startup
pgid, err := syscall.Getpgid(fim.Process.Pid)
if err == nil {
if err := syscall.Kill(-pgid, 9); err != nil {
log.Fatalf("failed to kill fim : %s", err)
}
}
}
break
}
// Control fim if we received a valid key for next/previous slide
if fimKey != "" {
_, err = io.WriteString(stdin, fimKey)
if err != nil {
log.Fatalf("Error controlling fim : %s", err)
}
ticker.Reset(pfconfig.Duration(config.CONFIG_KEY_SLIDESHOW_INTERVAL))
}
}
}
}(keyboardCtx)
// Restart fim after configured timeout ; This is setup as a ticker due to KemoNine not getting CommandWithContext stuff to work properly (lots of pointer related crashes and the like)
fimRestartDurationString := pfconfig.String(config.CONFIG_KEY_SLIDESHOW_RESTART_INTERVAL)
fimRestartDuration, err := time.ParseDuration(fimRestartDurationString)
if err != nil {
log.Fatalf("Error parsing restart duration : %S", err)
}
fimTicker := time.NewTicker(fimRestartDuration)
stop_fim_ticker := make(chan struct{})
go func() {
for {
select {
case <-fimTicker.C:
if fim != nil { // Just in case someone lays on exit key or similar during startup
pgid, err := syscall.Getpgid(fim.Process.Pid)
if err == nil {
if err := syscall.Kill(-pgid, 9); err != nil {
log.Fatalf("failed to kill fim : %s", err)
}
}
}
case <-stop_fim_ticker:
fimTicker.Stop()
return
}
}
}()
// Run fim
for !STOP_SLIDESHOW {
if err := fim.Run(); err != nil {
// Unwrap the error a bit so we can find out if a signal killed fim or something else
// An exit code of -1 means the program didn't exit in time or was terminated by a signal (per the docs)
if exitError, ok := err.(*exec.ExitError); ok && exitError.ExitCode() != -1 {
// We are NOT going to fatal here as there are many 'valid' reasons fim would exit for no good reason ;)
log.Printf("Error running fim : %s", err)
}
}
setupFim(PATH_TEMP_FOR_SLIDESHOW)
}
// Stop fim slideshow advancing go routine
close(stop_ticker)
// Stop restart go routine
close(stop_fim_ticker)
// Stop listening to keyboard events
keyboard.Close()
keyboardCancel()
}

3
utils/README.md Normal file
View File

@ -0,0 +1,3 @@
# Utils
Misc utilities used elsewhere by PiFrame go code.

16
utils/files.go Normal file
View File

@ -0,0 +1,16 @@
package utils
import (
"io/ioutil"
"log"
"os"
)
func WriteFile(destinationPath string, tempPath string, dataForDisk []byte) {
if err := ioutil.WriteFile(tempPath, dataForDisk, 0644); err != nil {
log.Fatalf("Error writing temp file : %s", err)
}
if err := os.Rename(tempPath, destinationPath); err != nil {
log.Fatalf("Error moving new in place : %s", err)
}
}

28
utils/hdmi.go Normal file
View File

@ -0,0 +1,28 @@
package utils
type SystemdTimer struct {
OnCalendar string
}
const SCREEN_ON_DOT_TIMER = `
[Unit]
Description=Turn on display
[Timer]
OnCalendar={{.OnCalendar}}
Persistent=true
[Install]
WantedBy=timers.target
`
const SCREEN_OFF_DOT_TIMER = `
[Unit]
Description=Turn off display
[Timer]
OnCalendar={{.OnCalendar}}
Persistent=true
[Install]
WantedBy=timers.target
`

23
utils/systemd.go Normal file
View File

@ -0,0 +1,23 @@
package utils
import (
"log"
"os/exec"
)
const (
CMD_SYSTEMCTL = "/usr/bin/systemctl"
)
const (
SCREEN_ON_TIMER_PATH = "/etc/systemd/system/screen-on.timer"
SCREEN_OFF_TIMER_PATH = "/etc/systemd/system/screen-off.timer"
)
func SystemdDaemonReload() {
// Reload systemd units
err := exec.Command(CMD_SYSTEMCTL, "daemon-reload").Run()
if err != nil {
log.Fatalf("Error running %s : %s", CMD_SYSTEMCTL, err)
}
}

41
utils/temperature.go Normal file
View File

@ -0,0 +1,41 @@
package utils
import (
"io/ioutil"
"log"
"os/exec"
"strconv"
"strings"
)
const (
CMD_VCGENCMD = "/opt/vc/bin/vcgencmd"
FILE_CPU_TEMP = "/sys/class/thermal/thermal_zone0/temp"
)
func GetCPUTemp() float64 {
cpuTempFileContents, err := ioutil.ReadFile(FILE_CPU_TEMP)
if err != nil {
log.Fatalf("Error getting CPU temp : %s", err)
}
cpuTempStr := strings.Trim(string(cpuTempFileContents), "\n")
cpuTempInt, err := strconv.Atoi(cpuTempStr)
if err != nil {
log.Fatalf("Error processing CPU temp : %S", err)
}
return float64(cpuTempInt) / 1000.0
}
func GetGPUTemp() float64 {
vcgencmdOut, err := exec.Command(CMD_VCGENCMD, "measure_temp").Output()
if err != nil {
log.Fatalf("Error getting GPU temp : %s", err)
}
gpuTempString := strings.Split(strings.Trim(string(vcgencmdOut), "\n"), "=")[1]
gpuTempString = strings.Trim(gpuTempString, "'C")
gpuTemp, err := strconv.ParseFloat(gpuTempString, 64)
if err != nil {
log.Fatalf("Error parsing GPU Temp : %s", err)
}
return gpuTemp
}

5
vendor/git.sungo.io/sungo/argon/LICENSE.md generated vendored Normal file
View File

@ -0,0 +1,5 @@
Software originally developed by sungo (https://sungo.io)
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

58
vendor/git.sungo.io/sungo/argon/fan/main.go generated vendored Normal file
View File

@ -0,0 +1,58 @@
package fan
// Code originally developed by sungo (https://sungo.io)
// Distributed under the terms of the 0BSD license https://opensource.org/licenses/0BSD
import (
"github.com/d2r2/go-i2c"
"github.com/d2r2/go-logger"
)
type Fan struct {
conn *i2c.I2C
}
func init() {
logger.ChangePackageLogLevel("i2c", logger.PanicLevel)
}
// From the argon python script:
// On an rpi4, the bus should be 1
// On any other rpi, the bus should 0
// On all platforms, the address should be 0x1a
func New(address uint8, bus int) (*Fan, error) {
f := &Fan{}
i, err := i2c.NewI2C(address, bus)
f.conn = i
return f, err
}
func (f *Fan) Bus() int {
return f.conn.GetBus()
}
func (f *Fan) Addr() uint8 {
return f.conn.GetAddr()
}
func (f *Fan) SafeClose() error {
f.SetSpeed(50)
return f.Close()
}
func (f *Fan) Close() error {
return f.conn.Close()
}
func (f *Fan) SetSpeed(percent int) error {
if percent > 100 {
percent = 100
} else if percent < 0 {
percent = 0
}
_, err := f.conn.WriteBytes([]byte(string(percent)))
return err
}

View File

@ -1,25 +0,0 @@
language: go
go:
# - "1.10"
- "tip"
# first part of the GOARCH workaround
# setting the GOARCH directly doesn't work, since the value will be overwritten later
# so set it to a temporary environment variable first
env:
global:
TRAVIS_CGO_ENABLED=1
TRAVIS_GOOS=linux
matrix:
- TRAVIS_GOARCH=amd64
- TRAVIS_GOARCH=arm TRAVIS_CC=arm-linux-gnueabi-gcc TRAVIS_GOARM=6
# second part of the GOARCH workaround
# now actually set the GOARCH env variable to the value of the temporary variable set earlier
before_install:
- sudo apt-get install gcc-arm-linux-gnueabi # for CGO cross compile to ARM
- export CGO_ENABLED=$TRAVIS_CGO_ENABLED GOARCH=$TRAVIS_GOARCH GOARM=$TRAVIS_GOARM GOOS=$TRAVIS_GOOS CC=$TRAVIS_CC
- go env # for debugging
- go tool dist env # for debugging

View File

@ -1,91 +0,0 @@
BH1750 ambient light sensor
=====================
[![Build Status](https://travis-ci.org/d2r2/go-bh1750.svg?branch=master)](https://travis-ci.org/d2r2/go-bh1750)
[![Go Report Card](https://goreportcard.com/badge/github.com/d2r2/go-bh1750)](https://goreportcard.com/report/github.com/d2r2/go-bh1750)
[![GoDoc](https://godoc.org/github.com/d2r2/go-bh1750?status.svg)](https://godoc.org/github.com/d2r2/go-bh1750)
[![MIT License](http://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
BH1750 ([general specification](https://raw.github.com/d2r2/go-bh1750/master/docs/bh1750fvi-e-186247.pdf)) is a power effective ambient light sensor with spectral response close to human eye. Sensor returns measured ambient light value in lux units. Easily integrated with Arduino and Raspberry PI via i2c communication interface:
![image](https://raw.github.com/d2r2/go-bh1750/master/docs/bh1750.jpg)
Here is a library written in [Go programming language](https://golang.org/) for Raspberry PI and counterparts, which gives you in the output ambient light value (making all necessary i2c-bus interacting and values computing).
Golang usage
------------
```go
func main() {
// Create new connection to i2c-bus on 0 line with address 0x23.
// Use i2cdetect utility to find device address over the i2c-bus
i2c, err := i2c.NewI2C(0x23, 0)
if err != nil {
log.Fatal(err)
}
defer i2c.Close()
sensor := bh1750.NewBH1750()
resolution := bh1750.HighResolution
amb, err := sensor.MeasureAmbientLight(i2c, resolution)
if err != nil {
lg.Fatal(err)
}
log.Printf("Ambient light (%s) = %v lx", resolution, amb)
```
Getting help
------------
GoDoc [documentation](http://godoc.org/github.com/d2r2/go-bh1750)
Installation
------------
```bash
$ go get -u github.com/d2r2/go-bh1750
```
Troubleshooting
--------------
- *How to obtain fresh Golang installation to RPi device (either any RPi clone):*
If your RaspberryPI golang installation taken by default from repository is outdated, you may consider
to install actual golang manually from official Golang [site](https://golang.org/dl/). Download
tar.gz file containing armv6l in the name. Follow installation instructions.
- *How to enable I2C bus on RPi device:*
If you employ RaspberryPI, use raspi-config utility to activate i2c-bus on the OS level.
Go to "Interfacing Options" menu, to active I2C bus.
Probably you will need to reboot to load i2c kernel module.
Finally you should have device like /dev/i2c-1 present in the system.
- *How to find I2C bus allocation and device address:*
Use i2cdetect utility in format "i2cdetect -y X", where X may vary from 0 to 5 or more,
to discover address occupied by peripheral device. To install utility you should run
`apt install i2c-tools` on debian-kind system. `i2cdetect -y 1` sample output:
```
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- 76 --
```
Contact
-------
Please use [Github issue tracker](https://github.com/d2r2/go-bh1750/issues) for filing bugs or feature requests.
License
-------
Go-bh1750 is licensed under MIT License.

View File

@ -1,323 +0,0 @@
//--------------------------------------------------------------------------------------------------
//
// Copyright (c) 2018 Denis Dyakov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//--------------------------------------------------------------------------------------------------
package bh1750
import (
"encoding/binary"
"errors"
"time"
i2c "github.com/d2r2/go-i2c"
"github.com/davecgh/go-spew/spew"
)
// Command bytes
const (
// No active state.
CMD_POWER_DOWN = 0x00
// Waiting for measurement command.
CMD_POWER_ON = 0x01
// Reset Data register value.
// Reset command is not acceptable in Power Down mode.
CMD_RESET = 0x07
// Start measurement at 1lx resolution.
// Measurement Time is typically 120ms.
CMD_CONTINUOUSLY_H_RES_MODE = 0x10
// Start measurement at 0.5lx resolution.
// Measurement Time is typically 120ms.
CMD_CONTINUOUSLY_H_RES_MODE2 = 0x11
// Start measurement at 4lx resolution.
// Measurement Time is typically 16ms.
CMD_CONTINUOUSLY_L_RES_MODE = 0x13
// Start measurement at 1lx resolution.
// Measurement Time is typically 120ms.
// It is automatically set to Power Down mode after measurement
CMD_ONE_TIME_H_RES_MODE = 0x20
// Start measurement at 0.5lx resolution.
// Measurement Time is typically 120ms.
// It is automatically set to Power Down mode after measurement.
CMD_ONE_TIME_H_RES_MODE2 = 0x21
// Start measurement at 4lx resolution.
// Measurement Time is typically 16ms.
// It is automatically set to Power Down mode after measurement.
CMD_ONE_TIME_L_RES_MODE = 0x23
// Change measurement time. 01000_MT[7,6,5]
CMD_CHANGE_MEAS_TIME_HIGH = 0x40
// Change measurement time. 011_MT[4,3,2,1,0]
CMD_CHANGE_MEAS_TIME_LOW = 0x60
)
// ResolutionMode define sensor sensitivity
// and measure time. Be aware, that improving
// sensitivity lead to increasing of measurement time.
type ResolutionMode int
const (
// LowResolution precision 4 lx, 16 ms measurement time
LowResolution ResolutionMode = iota + 1
// HighResolution precision 1 lx, 120 ms measurement time
HighResolution
// HighestResolution precision 0.5 lx, 120 ms measurement time
HighestResolution
)
// String define stringer interface.
func (v ResolutionMode) String() string {
switch v {
case LowResolution:
return "Low Resolution"
case HighResolution:
return "High Resolution"
case HighestResolution:
return "Highest Resolution"
default:
return "<unknown>"
}
}
// BH1750 it's a sensor itself.
type BH1750 struct {
// Since sensor have no register
// to report current state, we save
// last issued command to fill this gap.
lastCmd byte
lastResolution ResolutionMode
factor byte
}
// NewBH1750 return new sensor instance.
func NewBH1750() *BH1750 {
v := &BH1750{}
v.factor = v.GetDefaultSensivityFactor()
return v
}
// Reset clear ambient light register value.
func (v *BH1750) Reset(i2c *i2c.I2C) error {
lg.Debug("Reset sensor...")
_, err := i2c.WriteBytes([]byte{CMD_RESET})
if err != nil {
return err
}
v.lastCmd = CMD_RESET
time.Sleep(time.Microsecond * 3)
return nil
}
// PowerDown return register to idle state.
func (v *BH1750) PowerDown(i2c *i2c.I2C) error {
lg.Debug("Power down sensor...")
_, err := i2c.WriteBytes([]byte{CMD_POWER_DOWN})
if err != nil {
return err
}
v.lastCmd = CMD_POWER_DOWN
return nil
}
// PowerOn activate sensor.
func (v *BH1750) PowerOn(i2c *i2c.I2C) error {
lg.Debug("Power on sensor...")
_, err := i2c.WriteBytes([]byte{CMD_POWER_ON})
if err != nil {
return err
}
v.lastCmd = CMD_POWER_ON
return nil
}
// Get internal parameters used to program sensor.
func (v *BH1750) getResolutionData(resolution ResolutionMode) (cmd byte,
wait time.Duration, divider uint32) {
switch resolution {
case LowResolution:
cmd = CMD_ONE_TIME_L_RES_MODE
divider = 1
// typical measure time is 16 ms,
// but as it was found 24 ms max time
// gives better results
wait = time.Millisecond * 24
case HighResolution:
cmd = CMD_ONE_TIME_H_RES_MODE
divider = 1
// typical measure time
wait = time.Millisecond * 120
case HighestResolution:
cmd = CMD_ONE_TIME_H_RES_MODE2
divider = 2
// typical measure time
wait = time.Millisecond * 120
}
wait = wait * time.Duration(v.factor) /
time.Duration(v.GetDefaultSensivityFactor())
return cmd, wait, divider
}
// MeasureAmbientLight measure and return ambient light once in lux.
func (v *BH1750) MeasureAmbientLight(i2c *i2c.I2C,
resolution ResolutionMode) (uint16, error) {
lg.Debug("Run one time measure...")
cmd, wait, divider := v.getResolutionData(resolution)
v.lastCmd = cmd
v.lastResolution = resolution
_, err := i2c.WriteBytes([]byte{cmd})
if err != nil {
return 0, err
}
time.Sleep(wait)
var data struct {
Data [2]byte
}
err = readDataToStruct(i2c, 2, binary.BigEndian, &data)
if err != nil {
return 0, err
}
amb := uint16(uint32(uint16(data.Data[0])<<8|uint16(data.Data[1])) *
5 / 6 / divider)
return amb, nil
}
// StartMeasureAmbientLightContinuously start continuous
// measurement process. Use FetchMeasuredAmbientLight to get
// average ambient light amount collected and calculated over a time.
// Use PowerDown to stop measurements and return sensor to idle state.
func (v *BH1750) StartMeasureAmbientLightContinuously(i2c *i2c.I2C,
resolution ResolutionMode) (wait time.Duration, err error) {
lg.Debug("Start measures continuously...")
cmd, wait, _ := v.getResolutionData(resolution)
v.lastCmd = cmd
v.lastResolution = resolution
_, err = i2c.WriteBytes([]byte{cmd})
if err != nil {
return 0, err
}
// Wait first time to collect necessary
// amount of light for correct results.
// It's not necessary to wait next time
// same amount of time, because
// sensor accumulate average lux amount
// without any overwrite old value.
time.Sleep(wait)
// In any case we are returning same
// recommended amount of time to wait
// between measures.
return wait, nil
}
// FetchMeasuredAmbientLight return current average ambient light in lux.
// Previous command should be any continuous measurement initiation,
// otherwise error will be reported.
func (v *BH1750) FetchMeasuredAmbientLight(i2c *i2c.I2C) (uint16, error) {
lg.Debug("Fetch measured data...")
cmd, _, divider := v.getResolutionData(v.lastResolution)
if v.lastCmd != cmd {
return 0, errors.New(
"can't fetch measured ambient light, since last command doesn't match")
}
var data struct {
Data [2]byte
}
err := readDataToStruct(i2c, 2, binary.BigEndian, &data)
if err != nil {
return 0, err
}
amb := uint16(uint32(uint16(data.Data[0])<<8|uint16(data.Data[1])) *
5 / 6 / divider)
return amb, nil
}
// GetDefaultSensivityFactor return factor value
// used when your sensor have no any protection cover.
// This is default setting according to specification.
func (v *BH1750) GetDefaultSensivityFactor() byte {
return 69
}
// ChangeSensivityFactor used when you close sensor
// with protection cover, which change (ordinary decrease)
// expected amount of light falling on the sensor.
// In this case you should calibrate you sensor and find
// appropriate factor to get in output correct ambient light value.
// Be aware, that improving sensitivity will increase
// measurement time.
func (v *BH1750) ChangeSensivityFactor(i2c *i2c.I2C, factor byte) error {
lg.Debug("Change sensitivity factor...")
// minimum limit
const minValue = 31
// maximum limit
const maxValue = 254
if factor < minValue || factor > maxValue {
return errors.New(spew.Sprintf("sensitivity factor value exceed range [%d..%d]",
minValue, maxValue))
}
high := (factor & 0xE0) >> 5
_, err := i2c.WriteBytes([]byte{CMD_CHANGE_MEAS_TIME_HIGH | high})
if err != nil {
return err
}
low := (factor & 0x1F)
_, err = i2c.WriteBytes([]byte{CMD_CHANGE_MEAS_TIME_LOW | low})
if err != nil {
return err
}
v.factor = factor
return nil
}

View File

@ -1,10 +0,0 @@
package bh1750
import logger "github.com/d2r2/go-logger"
// You can manage verbosity of log output
// in the package by changing last parameter value.
var lg = logger.NewPackageLogger("bh1750",
logger.DebugLevel,
// logger.InfoLevel,
)

View File

@ -1,106 +0,0 @@
//--------------------------------------------------------------------------------------------------
//
// Copyright (c) 2018 Denis Dyakov
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
// associated documentation files (the "Software"), to deal in the Software without restriction,
// including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial
// portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
// BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
//--------------------------------------------------------------------------------------------------
package bh1750
import (
"bytes"
"encoding/binary"
"math"
i2c "github.com/d2r2/go-i2c"
)
// Utility functions
// getS16BE extract 2-byte integer as signed big-endian.
func getS16BE(buf []byte) int16 {
v := int16(buf[0])<<8 + int16(buf[1])
return v
}
// getS16LE extract 2-byte integer as signed little-endian.
func getS16LE(buf []byte) int16 {
w := getS16BE(buf)
// exchange bytes
v := (w&0xFF)<<8 + w>>8
return v
}
// getU16BE extract 2-byte integer as unsigned big-endian.
func getU16BE(buf []byte) uint16 {
v := uint16(buf[0])<<8 + uint16(buf[1])
return v
}
// getU16LE extract 2-byte integer as unsigned little-endian.
func getU16LE(buf []byte) uint16 {
w := getU16BE(buf)
// exchange bytes
v := (w&0xFF)<<8 + w>>8
return v
}
func calcCRC1(seed byte, buf []byte) byte {
for i := 0; i < len(buf); i++ {
b := buf[ /*len(buf)-1-*/ i]
for j := 0; j < 8; j++ {
if (seed^b)&0x01 != 0 {
seed ^= 0x18
seed >>= 1
seed |= 0x80
// crc = crc ^ 0x8c
} else {
seed >>= 1
}
b >>= 1
}
}
return seed
}
// Round float amount to certain precision.
func round64(value float64, precision int) float64 {
value2 := math.Round(value*math.Pow10(precision)) /
math.Pow10(precision)
return value2
}
// Round float amount to certain precision.
func round32(value float32, precision int) float32 {
return float32(round64(float64(value), precision))
}
// Read byte block from i2c device to struct object.
func readDataToStruct(i2c *i2c.I2C, byteCount int,
byteOrder binary.ByteOrder, obj interface{}) error {
buf1 := make([]byte, byteCount)
_, err := i2c.ReadBytes(buf1)
if err != nil {
return err
}
buf := bytes.NewBuffer(buf1)
err = binary.Read(buf, byteOrder, obj)
if err != nil {
return err
}
return nil
}

28
vendor/github.com/eiannone/keyboard/.gitignore generated vendored Normal file
View File

@ -0,0 +1,28 @@
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so
# Folders
_obj
_test
# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out
*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*
_testmain.go
*.exe
*.test
*.prof
# JetBrains IntelliJIdea
*.iml
.idea/

5
vendor/github.com/eiannone/keyboard/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,5 @@
# Please keep this file sorted.
Emanuele Iannone <emanuele@fondani.it>
Georg Reinke <guelfey@googlemail.com>
nsf <no.smile.face@gmail.com>

23
vendor/github.com/eiannone/keyboard/LICENSE generated vendored Normal file
View File

@ -0,0 +1,23 @@
The MIT License (MIT)
Copyright (C) 2012 termbox-go authors
Copyright (c) 2015 Emanuele Iannone
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

81
vendor/github.com/eiannone/keyboard/README.md generated vendored Normal file
View File

@ -0,0 +1,81 @@
# Keyboard
Simple library to listen for keystrokes from the keyboard
The code is inspired by [termbox-go](https://github.com/nsf/termbox-go) library.
### Installation
Install and update this go package with `go get -u github.com/eiannone/keyboard`
### Usage
Example of getting a single keystroke:
```go
char, _, err := keyboard.GetSingleKey()
if (err != nil) {
panic(err)
}
fmt.Printf("You pressed: %q\r\n", char)
```
Example of getting a series of keystrokes with a blocking `GetKey()` function:
```go
package main
import (
"fmt"
"github.com/eiannone/keyboard"
)
func main() {
if err := keyboard.Open(); err != nil {
panic(err)
}
defer func() {
_ = keyboard.Close()
}()
fmt.Println("Press ESC to quit")
for {
char, key, err := keyboard.GetKey()
if err != nil {
panic(err)
}
fmt.Printf("You pressed: rune %q, key %X\r\n", char, key)
if key == keyboard.KeyEsc {
break
}
}
}
```
Example of getting a series of keystrokes using a channel:
```go
package main
import (
"fmt"
"github.com/eiannone/keyboard"
)
func main() {
keysEvents, err := keyboard.GetKeys(10)
if err != nil {
panic(err)
}
defer func() {
_ = keyboard.Close()
}()
fmt.Println("Press ESC to quit")
for {
event := <-keysEvents
if event.Err != nil {
panic(event.Err)
}
fmt.Printf("You pressed: rune %q, key %X\r\n", event.Rune, event.Key)
if event.Key == keyboard.KeyEsc {
break
}
}
}
```

207
vendor/github.com/eiannone/keyboard/keyboard.go generated vendored Normal file
View File

@ -0,0 +1,207 @@
// +build !windows
package keyboard
import (
"errors"
"golang.org/x/sys/unix"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"unicode/utf8"
)
type (
input_event struct {
data []byte
err error
}
)
var (
out *os.File
in int
// term specific keys
keys []string
// termbox inner state
orig_tios unix.Termios
sigio = make(chan os.Signal, 1)
quitEvProd = make(chan bool)
quitConsole = make(chan bool)
inbuf = make([]byte, 0, 128)
input_buf = make(chan input_event)
)
func parse_escape_sequence(buf []byte) (size int, event KeyEvent) {
bufstr := string(buf)
for i, key := range keys {
if strings.HasPrefix(bufstr, key) {
event.Rune = 0
event.Key = Key(0xFFFF - i)
size = len(key)
return
}
}
return 0, event
}
func extract_event(inbuf []byte) (int, KeyEvent) {
if len(inbuf) == 0 {
return 0, KeyEvent{}
}
if inbuf[0] == '\033' {
if len(inbuf) == 1 {
return 1, KeyEvent{Key: KeyEsc}
}
// possible escape sequence
if size, event := parse_escape_sequence(inbuf); size != 0 {
return size, event
} else {
// it's not a recognized escape sequence, return error
i := 1 // check for multiple sequences in the buffer
for ; i < len(inbuf) && inbuf[i] != '\033'; i++ {
}
return i, KeyEvent{Key: KeyEsc, Err: errors.New("Unrecognized escape sequence")}
}
}
// if we're here, this is not an escape sequence and not an alt sequence
// so, it's a FUNCTIONAL KEY or a UNICODE character
// first of all check if it's a functional key
if Key(inbuf[0]) <= KeySpace || Key(inbuf[0]) == KeyBackspace2 {
return 1, KeyEvent{Key: Key(inbuf[0])}
}
// the only possible option is utf8 rune
if r, n := utf8.DecodeRune(inbuf); r != utf8.RuneError {
return n, KeyEvent{Rune: r}
}
return 0, KeyEvent{}
}
// Wait for an event and return it. This is a blocking function call.
func inputEventsProducer() {
for {
select {
case <-quitEvProd:
return
case ev := <-input_buf:
if ev.err != nil {
select {
case <-quitEvProd:
return
case inputComm <- KeyEvent{Err: ev.err}:
}
break
}
inbuf = append(inbuf, ev.data...)
for {
size, event := extract_event(inbuf)
if size > 0 {
select {
case <-quitEvProd:
return
case inputComm <- event:
}
copy(inbuf, inbuf[size:])
inbuf = inbuf[:len(inbuf)-size]
}
if size == 0 || len(inbuf) == 0 {
break
}
}
}
}
}
func initConsole() (err error) {
out, err = os.OpenFile("/dev/tty", unix.O_WRONLY, 0)
if err != nil {
return
}
in, err = syscall.Open("/dev/tty", unix.O_RDONLY, 0)
if err != nil {
return
}
err = setup_term()
if err != nil {
return errors.New("Error while reading terminfo data:" + err.Error())
}
signal.Notify(sigio, unix.SIGIO)
if _, err = unix.FcntlInt(uintptr(in), unix.F_SETFL, unix.O_ASYNC|unix.O_NONBLOCK); err != nil {
return
}
_, err = unix.FcntlInt(uintptr(in), unix.F_SETOWN, unix.Getpid())
if runtime.GOOS != "darwin" && err != nil {
return
}
if err = unix.IoctlSetTermios(int(out.Fd()), ioctl_GETATTR, &orig_tios); err != nil {
return
}
tios := orig_tios
tios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK |
unix.ISTRIP | unix.INLCR | unix.IGNCR |
unix.ICRNL | unix.IXON
tios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON |
unix.ISIG | unix.IEXTEN
tios.Cflag &^= unix.CSIZE | unix.PARENB
tios.Cflag |= unix.CS8
tios.Cc[unix.VMIN] = 1
tios.Cc[unix.VTIME] = 0
if err = unix.IoctlSetTermios(int(out.Fd()), ioctl_SETATTR, &tios); err != nil {
return
}
go func() {
buf := make([]byte, 128)
for {
select {
case <-quitConsole:
return
case <-sigio:
for {
bytesRead, err := syscall.Read(in, buf)
if err == unix.EAGAIN || err == unix.EWOULDBLOCK {
break
}
if err != nil {
bytesRead = 0
}
data := make([]byte, bytesRead)
copy(data, buf)
select {
case <-quitConsole:
return
case input_buf <- input_event{data, err}:
continue
}
}
}
}
}()
go inputEventsProducer()
return
}
func releaseConsole() {
quitConsole <- true
quitEvProd <- true
unix.IoctlSetTermios(int(out.Fd()), ioctl_SETATTR, &orig_tios)
out.Close()
unix.Close(in)
}

231
vendor/github.com/eiannone/keyboard/keyboard_common.go generated vendored Normal file
View File

@ -0,0 +1,231 @@
package keyboard
import (
"errors"
"time"
)
type (
Key uint16
KeyEvent struct {
Key Key // One of Key* constants, invalid if 'Ch' is not 0
Rune rune // A unicode character
Err error // Error in case if input failed
}
)
// Key constants, see GetKey() function.
const (
KeyF1 Key = 0xFFFF - iota
KeyF2
KeyF3
KeyF4
KeyF5
KeyF6
KeyF7
KeyF8
KeyF9
KeyF10
KeyF11
KeyF12
KeyInsert
KeyDelete
KeyHome
KeyEnd
KeyPgup
KeyPgdn
KeyArrowUp
KeyArrowDown
KeyArrowLeft
KeyArrowRight
key_min // see terminfo
)
const (
KeyCtrlTilde Key = 0x00
KeyCtrl2 Key = 0x00
KeyCtrlSpace Key = 0x00
KeyCtrlA Key = 0x01
KeyCtrlB Key = 0x02
KeyCtrlC Key = 0x03
KeyCtrlD Key = 0x04
KeyCtrlE Key = 0x05
KeyCtrlF Key = 0x06
KeyCtrlG Key = 0x07
KeyBackspace Key = 0x08
KeyCtrlH Key = 0x08
KeyTab Key = 0x09
KeyCtrlI Key = 0x09
KeyCtrlJ Key = 0x0A
KeyCtrlK Key = 0x0B
KeyCtrlL Key = 0x0C
KeyEnter Key = 0x0D
KeyCtrlM Key = 0x0D
KeyCtrlN Key = 0x0E
KeyCtrlO Key = 0x0F
KeyCtrlP Key = 0x10
KeyCtrlQ Key = 0x11
KeyCtrlR Key = 0x12
KeyCtrlS Key = 0x13
KeyCtrlT Key = 0x14
KeyCtrlU Key = 0x15
KeyCtrlV Key = 0x16
KeyCtrlW Key = 0x17
KeyCtrlX Key = 0x18
KeyCtrlY Key = 0x19
KeyCtrlZ Key = 0x1A
KeyEsc Key = 0x1B
KeyCtrlLsqBracket Key = 0x1B
KeyCtrl3 Key = 0x1B
KeyCtrl4 Key = 0x1C
KeyCtrlBackslash Key = 0x1C
KeyCtrl5 Key = 0x1D
KeyCtrlRsqBracket Key = 0x1D
KeyCtrl6 Key = 0x1E
KeyCtrl7 Key = 0x1F
KeyCtrlSlash Key = 0x1F
KeyCtrlUnderscore Key = 0x1F
KeySpace Key = 0x20
KeyBackspace2 Key = 0x7F
KeyCtrl8 Key = 0x7F
)
var (
inputComm chan KeyEvent
ping = make(chan bool)
doneClosing = make(chan bool, 1)
busy = make(chan bool)
waitingForKey = make(chan bool)
)
func IsStarted(timeout time.Duration) bool {
select {
case ping <- true:
return true
case <-time.After(timeout):
return false
}
}
func GetKeys(bufferSize int) (<-chan KeyEvent, error) {
if IsStarted(time.Millisecond * 1) {
if cap(inputComm) == bufferSize {
return inputComm, nil
}
return nil, errors.New("channel already started with a different capacity")
}
select {
case busy <- true:
return nil, errors.New("cannot open keyboard because program is busy")
default:
}
// Signal busy operation
go func() {
for <-busy {
} // Close the routine when busy is false
}()
inputComm = make(chan KeyEvent, bufferSize)
err := initConsole()
if err != nil {
close(inputComm)
busy <- false
return nil, err
}
// Signal ping subroutine started
go func() {
defer func() {
releaseConsole()
close(inputComm)
doneClosing <- true
}()
for <-ping {
} // Close the routine when ping is false
}()
busy <- false
// Wait for ping subroutine to start
ping <- true
return inputComm, nil
}
func Open() (err error) {
_, err = GetKeys(10)
return
}
// Should be called after successful initialization when functionality isn't required anymore.
func Close() (err error) {
// Checks if already closing
select {
case busy <- true:
return errors.New("cannot close keyboard because program is busy")
default:
}
// Checks if already closed
if !IsStarted(time.Millisecond * 1) {
return
}
// Signal busy operation
go func() {
for <-busy {
} // Close the routine when busy is false
}()
// Stop responding to ping and closes initial subroutine
ping <- false
// Cancel GetKey() operations
select {
case waitingForKey <- false:
break
default:
}
// Wait for closing finished
<-doneClosing
busy <- false
return
}
func GetKey() (rune, Key, error) {
// Check if opened
if !IsStarted(time.Millisecond * 50) {
return 0, 0, errors.New("keyboard not opened")
}
// Check if already waiting for key
select {
case waitingForKey <- true:
return 0, 0, errors.New("already waiting for key")
default:
}
for {
select {
case ev := <-inputComm:
return ev.Rune, ev.Key, ev.Err
case keepAlive := <-waitingForKey:
if !keepAlive {
return 0, 0, errors.New("operation canceled")
}
}
}
}
func GetSingleKey() (ch rune, key Key, err error) {
err = Open()
if err == nil {
ch, key, err = GetKey()
errClosing := Close()
if err == nil {
err = errClosing
}
}
return
}

285
vendor/github.com/eiannone/keyboard/keyboard_windows.go generated vendored Normal file
View File

@ -0,0 +1,285 @@
package keyboard
import (
"syscall"
"unsafe"
"golang.org/x/sys/windows"
)
const (
vk_backspace = 0x8
vk_tab = 0x9
vk_enter = 0xd
vk_esc = 0x1b
vk_space = 0x20
vk_pgup = 0x21
vk_pgdn = 0x22
vk_end = 0x23
vk_home = 0x24
vk_arrow_left = 0x25
vk_arrow_up = 0x26
vk_arrow_right = 0x27
vk_arrow_down = 0x28
vk_insert = 0x2d
vk_delete = 0x2e
vk_f1 = 0x70
vk_f2 = 0x71
vk_f3 = 0x72
vk_f4 = 0x73
vk_f5 = 0x74
vk_f6 = 0x75
vk_f7 = 0x76
vk_f8 = 0x77
vk_f9 = 0x78
vk_f10 = 0x79
vk_f11 = 0x7a
vk_f12 = 0x7b
right_alt_pressed = 0x1
left_alt_pressed = 0x2
right_ctrl_pressed = 0x4
left_ctrl_pressed = 0x8
shift_pressed = 0x10
k32_keyEvent = 0x1
)
type (
wchar uint16
dword uint32
word uint16
k32_event struct {
key_down int32
repeat_count word
virtual_key_code word
virtual_scan_code word
unicode_char wchar
control_key_state dword
}
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
k32_WaitForMultipleObjects = kernel32.NewProc("WaitForMultipleObjects")
k32_ReadConsoleInputW = kernel32.NewProc("ReadConsoleInputW")
hConsoleIn syscall.Handle
hInterrupt windows.Handle
quit = make(chan bool)
// This is just to prevent heap allocs at all costs
tmpArg dword
)
func getError(errno syscall.Errno) error {
if errno != 0 {
return error(errno)
} else {
return syscall.EINVAL
}
}
func getKeyEvent(r *k32_event) (KeyEvent, bool) {
e := KeyEvent{}
if r.key_down == 0 {
return e, false
}
ctrlPressed := r.control_key_state&(left_ctrl_pressed|right_ctrl_pressed) != 0
if r.virtual_key_code >= vk_f1 && r.virtual_key_code <= vk_f12 {
switch r.virtual_key_code {
case vk_f1:
e.Key = KeyF1
case vk_f2:
e.Key = KeyF2
case vk_f3:
e.Key = KeyF3
case vk_f4:
e.Key = KeyF4
case vk_f5:
e.Key = KeyF5
case vk_f6:
e.Key = KeyF6
case vk_f7:
e.Key = KeyF7
case vk_f8:
e.Key = KeyF8
case vk_f9:
e.Key = KeyF9
case vk_f10:
e.Key = KeyF10
case vk_f11:
e.Key = KeyF11
case vk_f12:
e.Key = KeyF12
default:
panic("unreachable")
}
return e, true
}
if r.virtual_key_code <= vk_delete {
switch r.virtual_key_code {
case vk_insert:
e.Key = KeyInsert
case vk_delete:
e.Key = KeyDelete
case vk_home:
e.Key = KeyHome
case vk_end:
e.Key = KeyEnd
case vk_pgup:
e.Key = KeyPgup
case vk_pgdn:
e.Key = KeyPgdn
case vk_arrow_up:
e.Key = KeyArrowUp
case vk_arrow_down:
e.Key = KeyArrowDown
case vk_arrow_left:
e.Key = KeyArrowLeft
case vk_arrow_right:
e.Key = KeyArrowRight
case vk_backspace:
if ctrlPressed {
e.Key = KeyBackspace2
} else {
e.Key = KeyBackspace
}
case vk_tab:
e.Key = KeyTab
case vk_enter:
e.Key = KeyEnter
case vk_esc:
e.Key = KeyEsc
case vk_space:
if ctrlPressed {
// manual return here, because KeyCtrlSpace is zero
e.Key = KeyCtrlSpace
return e, true
} else {
e.Key = KeySpace
}
}
if e.Key != 0 {
return e, true
}
}
if ctrlPressed {
if Key(r.unicode_char) >= KeyCtrlA && Key(r.unicode_char) <= KeyCtrlRsqBracket {
e.Key = Key(r.unicode_char)
return e, true
}
switch r.virtual_key_code {
case 192, 50:
// manual return here, because KeyCtrl2 is zero
e.Key = KeyCtrl2
return e, true
case 51:
e.Key = KeyCtrl3
case 52:
e.Key = KeyCtrl4
case 53:
e.Key = KeyCtrl5
case 54:
e.Key = KeyCtrl6
case 189, 191, 55:
e.Key = KeyCtrl7
case 8, 56:
e.Key = KeyCtrl8
}
if e.Key != 0 {
return e, true
}
}
if r.unicode_char != 0 {
e.Rune = rune(r.unicode_char)
return e, true
}
return e, false
}
func produceEvent(event KeyEvent) bool {
select {
case <-quit:
return false
case inputComm <- event:
return true
}
}
func inputEventsProducer() {
var input [20]uint16
for {
// Wait for events
// https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects
r0, _, e1 := syscall.Syscall6(k32_WaitForMultipleObjects.Addr(), 4,
uintptr(2), uintptr(unsafe.Pointer(&hConsoleIn)), 0, windows.INFINITE, 0, 0)
if uint32(r0) == windows.WAIT_FAILED && false == produceEvent(KeyEvent{Err: getError(e1)}) {
return
}
select {
case <-quit:
return
default:
}
// Get console input
r0, _, e1 = syscall.Syscall6(k32_ReadConsoleInputW.Addr(), 4,
uintptr(hConsoleIn), uintptr(unsafe.Pointer(&input[0])), 1, uintptr(unsafe.Pointer(&tmpArg)), 0, 0)
if int(r0) == 0 {
if false == produceEvent(KeyEvent{Err: getError(e1)}) {
return
}
} else if input[0] == k32_keyEvent {
kEvent := (*k32_event)(unsafe.Pointer(&input[2]))
ev, ok := getKeyEvent(kEvent)
if ok {
for i := 0; i < int(kEvent.repeat_count); i++ {
if false == produceEvent(ev) {
return
}
}
}
}
}
}
func initConsole() (err error) {
// Create an interrupt event
hInterrupt, err = windows.CreateEvent(nil, 0, 0, nil)
if err != nil {
return err
}
hConsoleIn, err = syscall.Open("CONIN$", windows.O_RDWR, 0)
if err != nil {
windows.Close(hInterrupt)
return
}
go inputEventsProducer()
return
}
func releaseConsole() {
// Stop events producer
windows.SetEvent(hInterrupt)
quit <- true
syscall.Close(hConsoleIn)
windows.Close(hInterrupt)
}

12
vendor/github.com/eiannone/keyboard/syscalls.go generated vendored Normal file
View File

@ -0,0 +1,12 @@
// +build !windows,!linux
package keyboard
import (
"golang.org/x/sys/unix"
)
const (
ioctl_GETATTR = unix.TIOCGETA
ioctl_SETATTR = unix.TIOCSETA
)

10
vendor/github.com/eiannone/keyboard/syscalls_linux.go generated vendored Normal file
View File

@ -0,0 +1,10 @@
package keyboard
import (
"golang.org/x/sys/unix"
)
const (
ioctl_GETATTR = unix.TCGETS
ioctl_SETATTR = unix.TCSETS
)

257
vendor/github.com/eiannone/keyboard/terminfo.go generated vendored Normal file
View File

@ -0,0 +1,257 @@
// +build !windows
// This file is imported from https://github.com/nsf/termbox-go
// Last update: 2020-04-30
// This file contains a simple and incomplete implementation of the terminfo
// database. Information was taken from the ncurses manpages term(5) and
// terminfo(5). Currently, only the string capabilities for special keys and for
// functions without parameters are actually used. Colors are still done with
// ANSI escape sequences. Other special features that are not (yet?) supported
// are reading from ~/.terminfo, the TERMINFO_DIRS variable, Berkeley database
// format and extended capabilities.
package keyboard
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"io/ioutil"
"os"
"strings"
)
const (
ti_header_length = 12
)
var (
eterm_keys = []string{
"\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
screen_keys = []string{
"\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1bOA", "\x1bOB", "\x1bOD", "\x1bOC",
}
xterm_keys = []string{
"\x1bOP", "\x1bOQ", "\x1bOR", "\x1bOS", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[H", "\x1b[F", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
rxvt_keys = []string{
"\x1b[11~", "\x1b[12~", "\x1b[13~", "\x1b[14~", "\x1b[15~", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[7~", "\x1b[8~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
linux_keys = []string{
"\x1b[[A", "\x1b[[B", "\x1b[[C", "\x1b[[D", "\x1b[[E", "\x1b[17~", "\x1b[18~", "\x1b[19~", "\x1b[20~", "\x1b[21~", "\x1b[23~", "\x1b[24~", "\x1b[2~", "\x1b[3~", "\x1b[1~", "\x1b[4~", "\x1b[5~", "\x1b[6~", "\x1b[A", "\x1b[B", "\x1b[D", "\x1b[C",
}
terms = []struct {
name string
keys []string
}{
{"Eterm", eterm_keys},
{"screen", screen_keys},
{"xterm", xterm_keys},
{"xterm-256color", xterm_keys},
{"rxvt-unicode", rxvt_keys},
{"rxvt-256color", rxvt_keys},
{"linux", linux_keys},
}
)
func load_terminfo() ([]byte, error) {
var data []byte
var err error
term := os.Getenv("TERM")
if term == "" {
return nil, errors.New("terminfo: TERM not set")
}
// Check if is a builtin terminal
for _, t := range terms {
if t.name == term {
return nil, errors.New("use built in!")
}
}
// The following behaviour follows the one described in terminfo(5) as
// distributed by ncurses.
terminfo := os.Getenv("TERMINFO")
if terminfo != "" {
// if TERMINFO is set, no other directory should be searched
return ti_try_path(terminfo)
}
// next, consider ~/.terminfo
home := os.Getenv("HOME")
if home != "" {
data, err = ti_try_path(home + "/.terminfo")
if err == nil {
return data, nil
}
}
// next, TERMINFO_DIRS
dirs := os.Getenv("TERMINFO_DIRS")
if dirs != "" {
for _, dir := range strings.Split(dirs, ":") {
if dir == "" {
// "" -> "/usr/share/terminfo"
dir = "/usr/share/terminfo"
}
data, err = ti_try_path(dir)
if err == nil {
return data, nil
}
}
}
// next, /lib/terminfo
data, err = ti_try_path("/lib/terminfo")
if err == nil {
return data, nil
}
// fall back to /usr/share/terminfo
return ti_try_path("/usr/share/terminfo")
}
func ti_try_path(path string) (data []byte, err error) {
// load_terminfo already made sure it is set
term := os.Getenv("TERM")
// first try, the typical *nix path
terminfo := path + "/" + term[0:1] + "/" + term
data, err = ioutil.ReadFile(terminfo)
if err == nil {
return
}
// fallback to darwin specific dirs structure
terminfo = path + "/" + hex.EncodeToString([]byte(term[:1])) + "/" + term
data, err = ioutil.ReadFile(terminfo)
return
}
func setup_term_builtin() error {
name := os.Getenv("TERM")
if name == "" {
return errors.New("terminfo: TERM environment variable not set")
}
for _, t := range terms {
if t.name == name {
keys = t.keys
return nil
}
}
compat_table := []struct {
partial string
keys []string
}{
{"xterm", xterm_keys},
{"xterm-256color", xterm_keys},
{"rxvt", rxvt_keys},
{"rxvt-unicode", rxvt_keys},
{"rxvt-256color", rxvt_keys},
{"linux", linux_keys},
{"Eterm", eterm_keys},
{"screen", screen_keys},
// let's assume that 'cygwin' is xterm compatible
{"cygwin", xterm_keys},
{"st", xterm_keys},
}
// try compatibility variants
for _, it := range compat_table {
if strings.Contains(name, it.partial) {
keys = it.keys
return nil
}
}
return errors.New("termbox: unsupported terminal")
}
func setup_term() (err error) {
var data []byte
var header [6]int16
var str_offset, table_offset int16
data, err = load_terminfo()
if err != nil {
return setup_term_builtin()
}
rd := bytes.NewReader(data)
// 0: magic number, 1: size of names section, 2: size of boolean section, 3:
// size of numbers section (in integers), 4: size of the strings section (in
// integers), 5: size of the string table
err = binary.Read(rd, binary.LittleEndian, header[:])
if err != nil {
return
}
if header[0] != 542 && header[0] != 282 {
return setup_term_builtin()
}
number_sec_len := int16(2)
if header[0] == 542 { // doc says it should be octal 0542, but what I see it terminfo files is 542, learn to program please... thank you..
number_sec_len = 4
}
if (header[1]+header[2])%2 != 0 {
// old quirk to align everything on word boundaries
header[2] += 1
}
str_offset = ti_header_length + header[1] + header[2] + number_sec_len*header[3]
table_offset = str_offset + 2*header[4]
keys = make([]string, 0xFFFF-key_min)
for i := range keys {
keys[i], err = ti_read_string(rd, str_offset+2*ti_keys[i], table_offset)
if err != nil {
return
}
}
return nil
}
func ti_read_string(rd *bytes.Reader, str_off, table int16) (string, error) {
var off int16
_, err := rd.Seek(int64(str_off), 0)
if err != nil {
return "", err
}
err = binary.Read(rd, binary.LittleEndian, &off)
if err != nil {
return "", err
}
_, err = rd.Seek(int64(table+off), 0)
if err != nil {
return "", err
}
var bs []byte
for {
b, err := rd.ReadByte()
if err != nil {
return "", err
}
if b == byte(0x00) {
break
}
bs = append(bs, b)
}
return string(bs), nil
}
// "Maps" special keys constants from termbox.go to the number of the respective
// string capability in the terminfo file. Taken from (ncurses) term.h.
var ti_keys = []int16{
66, 68 /* apparently not a typo; 67 is F10 for whatever reason */, 69, 70,
71, 72, 73, 74, 75, 67, 216, 217, 77, 59, 76, 164, 82, 81, 87, 61, 79, 83,
}

12
vendor/github.com/fsnotify/fsnotify/.editorconfig generated vendored Normal file
View File

@ -0,0 +1,12 @@
root = true
[*.go]
indent_style = tab
indent_size = 4
insert_final_newline = true
[*.{yml,yaml}]
indent_style = space
indent_size = 2
insert_final_newline = true
trim_trailing_whitespace = true

1
vendor/github.com/fsnotify/fsnotify/.gitattributes generated vendored Normal file
View File

@ -0,0 +1 @@
go.sum linguist-generated

6
vendor/github.com/fsnotify/fsnotify/.gitignore generated vendored Normal file
View File

@ -0,0 +1,6 @@
# Setup a Global .gitignore for OS and editor generated files:
# https://help.github.com/articles/ignoring-files
# git config --global core.excludesfile ~/.gitignore_global
.vagrant
*.sublime-project

36
vendor/github.com/fsnotify/fsnotify/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,36 @@
sudo: false
language: go
go:
- "stable"
- "1.11.x"
- "1.10.x"
- "1.9.x"
matrix:
include:
- go: "stable"
env: GOLINT=true
allow_failures:
- go: tip
fast_finish: true
before_install:
- if [ ! -z "${GOLINT}" ]; then go get -u golang.org/x/lint/golint; fi
script:
- go test --race ./...
after_script:
- test -z "$(gofmt -s -l -w . | tee /dev/stderr)"
- if [ ! -z "${GOLINT}" ]; then echo running golint; golint --set_exit_status ./...; else echo skipping golint; fi
- go vet ./...
os:
- linux
- osx
- windows
notifications:
email: false

52
vendor/github.com/fsnotify/fsnotify/AUTHORS generated vendored Normal file
View File

@ -0,0 +1,52 @@
# Names should be added to this file as
# Name or Organization <email address>
# The email address is not required for organizations.
# You can update this list using the following command:
#
# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
# Please keep the list sorted.
Aaron L <aaron@bettercoder.net>
Adrien Bustany <adrien@bustany.org>
Amit Krishnan <amit.krishnan@oracle.com>
Anmol Sethi <me@anmol.io>
Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
Bruno Bigras <bigras.bruno@gmail.com>
Caleb Spare <cespare@gmail.com>
Case Nelson <case@teammating.com>
Chris Howey <chris@howey.me> <howeyc@gmail.com>
Christoffer Buchholz <christoffer.buchholz@gmail.com>
Daniel Wagner-Hall <dawagner@gmail.com>
Dave Cheney <dave@cheney.net>
Evan Phoenix <evan@fallingsnow.net>
Francisco Souza <f@souza.cc>
Hari haran <hariharan.uno@gmail.com>
John C Barstow
Kelvin Fo <vmirage@gmail.com>
Ken-ichirou MATSUZAWA <chamas@h4.dion.ne.jp>
Matt Layher <mdlayher@gmail.com>
Nathan Youngman <git@nathany.com>
Nickolai Zeldovich <nickolai@csail.mit.edu>
Patrick <patrick@dropbox.com>
Paul Hammond <paul@paulhammond.org>
Pawel Knap <pawelknap88@gmail.com>
Pieter Droogendijk <pieter@binky.org.uk>
Pursuit92 <JoshChase@techpursuit.net>
Riku Voipio <riku.voipio@linaro.org>
Rob Figueiredo <robfig@gmail.com>
Rodrigo Chiossi <rodrigochiossi@gmail.com>
Slawek Ligus <root@ooz.ie>
Soge Zhang <zhssoge@gmail.com>
Tiffany Jernigan <tiffany.jernigan@intel.com>
Tilak Sharma <tilaks@google.com>
Tom Payne <twpayne@gmail.com>
Travis Cline <travis.cline@gmail.com>
Tudor Golubenco <tudor.g@gmail.com>
Vahe Khachikyan <vahe@live.ca>
Yukang <moorekang@gmail.com>
bronze1man <bronze1man@gmail.com>
debrando <denis.brandolini@gmail.com>
henrikedwards <henrik.edwards@gmail.com>
铁哥 <guotie.9@gmail.com>

317
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,317 @@
# Changelog
## v1.4.7 / 2018-01-09
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
* Tests: Fix missing verb on format string (thanks @rchiossi)
* Linux: Fix deadlock in Remove (thanks @aarondl)
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
* Docs: Moved FAQ into the README (thanks @vahe)
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
* Docs: replace references to OS X with macOS
## v1.4.2 / 2016-10-10
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
## v1.4.1 / 2016-10-04
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
## v1.4.0 / 2016-10-01
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
## v1.3.1 / 2016-06-28
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
## v1.3.0 / 2016-04-19
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
## v1.2.10 / 2016-03-02
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
## v1.2.9 / 2016-01-13
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
## v1.2.8 / 2015-12-17
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
* inotify: fix race in test
* enable race detection for continuous integration (Linux, Mac, Windows)
## v1.2.5 / 2015-10-17
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
## v1.2.1 / 2015-10-14
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
## v1.2.0 / 2015-02-08
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
## v1.1.1 / 2015-02-05
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
## v1.1.0 / 2014-12-12
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
* add low-level functions
* only need to store flags on directories
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
* done can be an unbuffered channel
* remove calls to os.NewSyscallError
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v1.0.4 / 2014-09-07
* kqueue: add dragonfly to the build tags.
* Rename source code files, rearrange code so exported APIs are at the top.
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
## v1.0.3 / 2014-08-19
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
## v1.0.2 / 2014-08-17
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
## v1.0.0 / 2014-08-15
* [API] Remove AddWatch on Windows, use Add.
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
* Minor updates based on feedback from golint.
## dev / 2014-07-09
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
## dev / 2014-07-04
* kqueue: fix incorrect mutex used in Close()
* Update example to demonstrate usage of Op.
## dev / 2014-06-28
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
* Fix for String() method on Event (thanks Alex Brainman)
* Don't build on Plan 9 or Solaris (thanks @4ad)
## dev / 2014-06-21
* Events channel of type Event rather than *Event.
* [internal] use syscall constants directly for inotify and kqueue.
* [internal] kqueue: rename events to kevents and fileEvent to event.
## dev / 2014-06-19
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
* [internal] remove cookie from Event struct (unused).
* [internal] Event struct has the same definition across every OS.
* [internal] remove internal watch and removeWatch methods.
## dev / 2014-06-12
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
* [API] Pluralized channel names: Events and Errors.
* [API] Renamed FileEvent struct to Event.
* [API] Op constants replace methods like IsCreate().
## dev / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## dev / 2014-05-23
* [API] Remove current implementation of WatchFlags.
* current implementation doesn't take advantage of OS for efficiency
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
* no tests for the current implementation
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
## v0.9.3 / 2014-12-31
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
## v0.9.2 / 2014-08-17
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
## v0.9.1 / 2014-06-12
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
## v0.9.0 / 2014-01-17
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
## v0.8.12 / 2013-11-13
* [API] Remove FD_SET and friends from Linux adapter
## v0.8.11 / 2013-11-02
* [Doc] Add Changelog [#72][] (thanks @nathany)
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
## v0.8.10 / 2013-10-19
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
* [Doc] specify OS-specific limits in README (thanks @debrando)
## v0.8.9 / 2013-09-08
* [Doc] Contributing (thanks @nathany)
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
* [Doc] GoCI badge in README (Linux only) [#60][]
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
## v0.8.8 / 2013-06-17
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
## v0.8.7 / 2013-06-03
* [API] Make syscall flags internal
* [Fix] inotify: ignore event changes
* [Fix] race in symlink test [#45][] (reported by @srid)
* [Fix] tests on Windows
* lower case error messages
## v0.8.6 / 2013-05-23
* kqueue: Use EVT_ONLY flag on Darwin
* [Doc] Update README with full example
## v0.8.5 / 2013-05-09
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
## v0.8.4 / 2013-04-07
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
## v0.8.3 / 2013-03-13
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
## v0.8.2 / 2013-02-07
* [Doc] add Authors
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
## v0.8.1 / 2013-01-09
* [Fix] Windows path separators
* [Doc] BSD License
## v0.8.0 / 2012-11-09
* kqueue: directory watching improvements (thanks @vmirage)
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
## v0.7.4 / 2012-10-09
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
* [Fix] kqueue: modify after recreation of file
## v0.7.3 / 2012-09-27
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
* [Fix] kqueue: no longer get duplicate CREATE events
## v0.7.2 / 2012-09-01
* kqueue: events for created directories
## v0.7.1 / 2012-07-14
* [Fix] for renaming files
## v0.7.0 / 2012-07-02
* [Feature] FSNotify flags
* [Fix] inotify: Added file name back to event path
## v0.6.0 / 2012-06-06
* kqueue: watch files after directory created (thanks @tmc)
## v0.5.1 / 2012-05-22
* [Fix] inotify: remove all watches before Close()
## v0.5.0 / 2012-05-03
* [API] kqueue: return errors during watch instead of sending over channel
* kqueue: match symlink behavior on Linux
* inotify: add `DELETE_SELF` (requested by @taralx)
* [Fix] kqueue: handle EINTR (reported by @robfig)
* [Doc] Godoc example [#1][] (thanks @davecheney)
## v0.4.0 / 2012-03-30
* Go 1 released: build with go tool
* [Feature] Windows support using winfsnotify
* Windows does not have attribute change notifications
* Roll attribute notifications into IsModify
## v0.3.0 / 2012-02-19
* kqueue: add files when watch directory
## v0.2.0 / 2011-12-30
* update to latest Go weekly code
## v0.1.0 / 2011-10-19
* kqueue: add watch on file creation to match inotify
* kqueue: create file event
* inotify: ignore `IN_IGNORED` events
* event String()
* linux: common FileEvent functions
* initial commit
[#79]: https://github.com/howeyc/fsnotify/pull/79
[#77]: https://github.com/howeyc/fsnotify/pull/77
[#72]: https://github.com/howeyc/fsnotify/issues/72
[#71]: https://github.com/howeyc/fsnotify/issues/71
[#70]: https://github.com/howeyc/fsnotify/issues/70
[#63]: https://github.com/howeyc/fsnotify/issues/63
[#62]: https://github.com/howeyc/fsnotify/issues/62
[#60]: https://github.com/howeyc/fsnotify/issues/60
[#59]: https://github.com/howeyc/fsnotify/issues/59
[#49]: https://github.com/howeyc/fsnotify/issues/49
[#45]: https://github.com/howeyc/fsnotify/issues/45
[#40]: https://github.com/howeyc/fsnotify/issues/40
[#36]: https://github.com/howeyc/fsnotify/issues/36
[#33]: https://github.com/howeyc/fsnotify/issues/33
[#29]: https://github.com/howeyc/fsnotify/issues/29
[#25]: https://github.com/howeyc/fsnotify/issues/25
[#24]: https://github.com/howeyc/fsnotify/issues/24
[#21]: https://github.com/howeyc/fsnotify/issues/21

77
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,77 @@
# Contributing
## Issues
* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/fsnotify/fsnotify/issues).
* Please indicate the platform you are using fsnotify on.
* A code example to reproduce the problem is appreciated.
## Pull Requests
### Contributor License Agreement
fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
Please indicate that you have signed the CLA in your pull request.
### How fsnotify is Developed
* Development is done on feature branches.
* Tests are run on BSD, Linux, macOS and Windows.
* Pull requests are reviewed and [applied to master][am] using [hub][].
* Maintainers may modify or squash commits rather than asking contributors to.
* To issue a new release, the maintainers will:
* Update the CHANGELOG
* Tag a version, which will become available through gopkg.in.
### How to Fork
For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
1. Install from GitHub (`go get -u github.com/fsnotify/fsnotify`)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Ensure everything works and the tests pass (see below)
4. Commit your changes (`git commit -am 'Add some feature'`)
Contribute upstream:
1. Fork fsnotify on GitHub
2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
3. Push to the branch (`git push fork my-new-feature`)
4. Create a new Pull Request on GitHub
This workflow is [thoroughly explained by Katrina Owen](https://splice.com/blog/contributing-open-source-git-repositories-go/).
### Testing
fsnotify uses build tags to compile different code on Linux, BSD, macOS, and Windows.
Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd fsnotify/fsnotify; go test'`.
* When you're done, you will want to halt or destroy the Vagrant boxes.
Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
Right now there is no equivalent solution for Windows and macOS, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
### Maintainers
Help maintaining fsnotify is welcome. To be a maintainer:
* Submit a pull request and sign the CLA as above.
* You must be able to run the test suite on Mac, Windows, Linux and BSD.
To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful][am]. This requires installing [hub][].
All code changes should be internal pull requests.
Releases are tagged using [Semantic Versioning](http://semver.org/).
[hub]: https://github.com/github/hub
[am]: http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs

28
vendor/github.com/fsnotify/fsnotify/LICENSE generated vendored Normal file
View File

@ -0,0 +1,28 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Copyright (c) 2012-2019 fsnotify Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

130
vendor/github.com/fsnotify/fsnotify/README.md generated vendored Normal file
View File

@ -0,0 +1,130 @@
# File system notifications for Go
[![GoDoc](https://godoc.org/github.com/fsnotify/fsnotify?status.svg)](https://godoc.org/github.com/fsnotify/fsnotify) [![Go Report Card](https://goreportcard.com/badge/github.com/fsnotify/fsnotify)](https://goreportcard.com/report/github.com/fsnotify/fsnotify)
fsnotify utilizes [golang.org/x/sys](https://godoc.org/golang.org/x/sys) rather than `syscall` from the standard library. Ensure you have the latest version installed by running:
```console
go get -u golang.org/x/sys/...
```
Cross platform: Windows, Linux, BSD and macOS.
| Adapter | OS | Status |
| --------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| inotify | Linux 2.6.27 or later, Android\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| kqueue | BSD, macOS, iOS\* | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| ReadDirectoryChangesW | Windows | Supported [![Build Status](https://travis-ci.org/fsnotify/fsnotify.svg?branch=master)](https://travis-ci.org/fsnotify/fsnotify) |
| FSEvents | macOS | [Planned](https://github.com/fsnotify/fsnotify/issues/11) |
| FEN | Solaris 11 | [In Progress](https://github.com/fsnotify/fsnotify/issues/12) |
| fanotify | Linux 2.6.37+ | [Planned](https://github.com/fsnotify/fsnotify/issues/114) |
| USN Journals | Windows | [Maybe](https://github.com/fsnotify/fsnotify/issues/53) |
| Polling | *All* | [Maybe](https://github.com/fsnotify/fsnotify/issues/9) |
\* Android and iOS are untested.
Please see [the documentation](https://godoc.org/github.com/fsnotify/fsnotify) and consult the [FAQ](#faq) for usage information.
## API stability
fsnotify is a fork of [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify) with a new API as of v1.0. The API is based on [this design document](http://goo.gl/MrYxyA).
All [releases](https://github.com/fsnotify/fsnotify/releases) are tagged based on [Semantic Versioning](http://semver.org/). Further API changes are [planned](https://github.com/fsnotify/fsnotify/milestones), and will be tagged with a new major revision number.
Go 1.6 supports dependencies located in the `vendor/` folder. Unless you are creating a library, it is recommended that you copy fsnotify into `vendor/github.com/fsnotify/fsnotify` within your project, and likewise for `golang.org/x/sys`.
## Usage
```go
package main
import (
"log"
"github.com/fsnotify/fsnotify"
)
func main() {
watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}
defer watcher.Close()
done := make(chan bool)
go func() {
for {
select {
case event, ok := <-watcher.Events:
if !ok {
return
}
log.Println("event:", event)
if event.Op&fsnotify.Write == fsnotify.Write {
log.Println("modified file:", event.Name)
}
case err, ok := <-watcher.Errors:
if !ok {
return
}
log.Println("error:", err)
}
}
}()
err = watcher.Add("/tmp/foo")
if err != nil {
log.Fatal(err)
}
<-done
}
```
## Contributing
Please refer to [CONTRIBUTING][] before opening an issue or pull request.
## Example
See [example_test.go](https://github.com/fsnotify/fsnotify/blob/master/example_test.go).
## FAQ
**When a file is moved to another directory is it still being watched?**
No (it shouldn't be, unless you are watching where it was moved to).
**When I watch a directory, are all subdirectories watched as well?**
No, you must add watches for any directory you want to watch (a recursive watcher is on the roadmap [#18][]).
**Do I have to watch the Error and Event channels in a separate goroutine?**
As of now, yes. Looking into making this single-thread friendly (see [howeyc #7][#7])
**Why am I receiving multiple events for the same file on OS X?**
Spotlight indexing on OS X can result in multiple events (see [howeyc #62][#62]). A temporary workaround is to add your folder(s) to the *Spotlight Privacy settings* until we have a native FSEvents implementation (see [#11][]).
**How many files can be watched at once?**
There are OS-specific limits as to how many watches can be created:
* Linux: /proc/sys/fs/inotify/max_user_watches contains the limit, reaching this limit results in a "no space left on device" error.
* BSD / OSX: sysctl variables "kern.maxfiles" and "kern.maxfilesperproc", reaching these limits results in a "too many open files" error.
**Why don't notifications work with NFS filesystems or filesystem in userspace (FUSE)?**
fsnotify requires support from underlying OS to work. The current NFS protocol does not provide network level support for file notifications.
[#62]: https://github.com/howeyc/fsnotify/issues/62
[#18]: https://github.com/fsnotify/fsnotify/issues/18
[#11]: https://github.com/fsnotify/fsnotify/issues/11
[#7]: https://github.com/howeyc/fsnotify/issues/7
[contributing]: https://github.com/fsnotify/fsnotify/blob/master/CONTRIBUTING.md
## Related Projects
* [notify](https://github.com/rjeczalik/notify)
* [fsevents](https://github.com/fsnotify/fsevents)

37
vendor/github.com/fsnotify/fsnotify/fen.go generated vendored Normal file
View File

@ -0,0 +1,37 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build solaris
package fsnotify
import (
"errors"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
return nil, errors.New("FEN based watcher not yet supported for fsnotify\n")
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
return nil
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
return nil
}

68
vendor/github.com/fsnotify/fsnotify/fsnotify.go generated vendored Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !plan9
// Package fsnotify provides a platform-independent interface for file system notifications.
package fsnotify
import (
"bytes"
"errors"
"fmt"
)
// Event represents a single file system notification.
type Event struct {
Name string // Relative path to the file or directory.
Op Op // File operation that triggered the event.
}
// Op describes a set of file operations.
type Op uint32
// These are the generalized file operations that can trigger a notification.
const (
Create Op = 1 << iota
Write
Remove
Rename
Chmod
)
func (op Op) String() string {
// Use a buffer for efficient string concatenation
var buffer bytes.Buffer
if op&Create == Create {
buffer.WriteString("|CREATE")
}
if op&Remove == Remove {
buffer.WriteString("|REMOVE")
}
if op&Write == Write {
buffer.WriteString("|WRITE")
}
if op&Rename == Rename {
buffer.WriteString("|RENAME")
}
if op&Chmod == Chmod {
buffer.WriteString("|CHMOD")
}
if buffer.Len() == 0 {
return ""
}
return buffer.String()[1:] // Strip leading pipe
}
// String returns a string representation of the event in the form
// "file: REMOVE|WRITE|..."
func (e Event) String() string {
return fmt.Sprintf("%q: %s", e.Name, e.Op.String())
}
// Common errors that can be reported by a watcher
var (
ErrEventOverflow = errors.New("fsnotify queue overflow")
)

5
vendor/github.com/fsnotify/fsnotify/go.mod generated vendored Normal file
View File

@ -0,0 +1,5 @@
module github.com/fsnotify/fsnotify
go 1.13
require golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9

2
vendor/github.com/fsnotify/fsnotify/go.sum generated vendored Normal file
View File

@ -0,0 +1,2 @@
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9 h1:L2auWcuQIvxz9xSEqzESnV/QN/gNRXNApHi3fYwl2w0=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

337
vendor/github.com/fsnotify/fsnotify/inotify.go generated vendored Normal file
View File

@ -0,0 +1,337 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"sync"
"unsafe"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
mu sync.Mutex // Map access
fd int
poller *fdPoller
watches map[string]*watch // Map of inotify watches (key: path)
paths map[int]string // Map of watched paths (key: watch descriptor)
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
doneResp chan struct{} // Channel to respond to Close
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
// Create inotify fd
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC)
if fd == -1 {
return nil, errno
}
// Create epoll
poller, err := newFdPoller(fd)
if err != nil {
unix.Close(fd)
return nil, err
}
w := &Watcher{
fd: fd,
poller: poller,
watches: make(map[string]*watch),
paths: make(map[int]string),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
doneResp: make(chan struct{}),
}
go w.readEvents()
return w, nil
}
func (w *Watcher) isClosed() bool {
select {
case <-w.done:
return true
default:
return false
}
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed() {
return nil
}
// Send 'close' signal to goroutine, and set the Watcher to closed.
close(w.done)
// Wake up goroutine
w.poller.wake()
// Wait for goroutine to close
<-w.doneResp
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
name = filepath.Clean(name)
if w.isClosed() {
return errors.New("inotify instance already closed")
}
const agnosticEvents = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
var flags uint32 = agnosticEvents
w.mu.Lock()
defer w.mu.Unlock()
watchEntry := w.watches[name]
if watchEntry != nil {
flags |= watchEntry.flags | unix.IN_MASK_ADD
}
wd, errno := unix.InotifyAddWatch(w.fd, name, flags)
if wd == -1 {
return errno
}
if watchEntry == nil {
w.watches[name] = &watch{wd: uint32(wd), flags: flags}
w.paths[wd] = name
} else {
watchEntry.wd = uint32(wd)
watchEntry.flags = flags
}
return nil
}
// Remove stops watching the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
// Fetch the watch.
w.mu.Lock()
defer w.mu.Unlock()
watch, ok := w.watches[name]
// Remove it from inotify.
if !ok {
return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
}
// We successfully removed the watch if InotifyRmWatch doesn't return an
// error, we need to clean up our internal state to ensure it matches
// inotify's kernel state.
delete(w.paths, int(watch.wd))
delete(w.watches, name)
// inotify_rm_watch will return EINVAL if the file has been deleted;
// the inotify will already have been removed.
// watches and pathes are deleted in ignoreLinux() implicitly and asynchronously
// by calling inotify_rm_watch() below. e.g. readEvents() goroutine receives IN_IGNORE
// so that EINVAL means that the wd is being rm_watch()ed or its file removed
// by another thread and we have not received IN_IGNORE event.
success, errno := unix.InotifyRmWatch(w.fd, watch.wd)
if success == -1 {
// TODO: Perhaps it's not helpful to return an error here in every case.
// the only two possible errors are:
// EBADF, which happens when w.fd is not a valid file descriptor of any kind.
// EINVAL, which is when fd is not an inotify descriptor or wd is not a valid watch descriptor.
// Watch descriptors are invalidated when they are removed explicitly or implicitly;
// explicitly by inotify_rm_watch, implicitly when the file they are watching is deleted.
return errno
}
return nil
}
type watch struct {
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
}
// readEvents reads from the inotify file descriptor, converts the
// received events into Event objects and sends them via the Events channel
func (w *Watcher) readEvents() {
var (
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
n int // Number of bytes read with read()
errno error // Syscall errno
ok bool // For poller.wait
)
defer close(w.doneResp)
defer close(w.Errors)
defer close(w.Events)
defer unix.Close(w.fd)
defer w.poller.close()
for {
// See if we have been closed.
if w.isClosed() {
return
}
ok, errno = w.poller.wait()
if errno != nil {
select {
case w.Errors <- errno:
case <-w.done:
return
}
continue
}
if !ok {
continue
}
n, errno = unix.Read(w.fd, buf[:])
// If a signal interrupted execution, see if we've been asked to close, and try again.
// http://man7.org/linux/man-pages/man7/signal.7.html :
// "Before Linux 3.8, reads from an inotify(7) file descriptor were not restartable"
if errno == unix.EINTR {
continue
}
// unix.Read might have been woken up by Close. If so, we're done.
if w.isClosed() {
return
}
if n < unix.SizeofInotifyEvent {
var err error
if n == 0 {
// If EOF is received. This should really never happen.
err = io.EOF
} else if n < 0 {
// If an error occurred while reading.
err = errno
} else {
// Read was too short.
err = errors.New("notify: short read in readEvents()")
}
select {
case w.Errors <- err:
case <-w.done:
return
}
continue
}
var offset uint32
// We don't know how many events we just read into the buffer
// While the offset points to at least one whole event...
for offset <= uint32(n-unix.SizeofInotifyEvent) {
// Point "raw" to the event in the buffer
raw := (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
mask := uint32(raw.Mask)
nameLen := uint32(raw.Len)
if mask&unix.IN_Q_OVERFLOW != 0 {
select {
case w.Errors <- ErrEventOverflow:
case <-w.done:
return
}
}
// If the event happened to the watched directory or the watched file, the kernel
// doesn't append the filename to the event, but we would like to always fill the
// the "Name" field with a valid filename. We retrieve the path of the watch from
// the "paths" map.
w.mu.Lock()
name, ok := w.paths[int(raw.Wd)]
// IN_DELETE_SELF occurs when the file/directory being watched is removed.
// This is a sign to clean up the maps, otherwise we are no longer in sync
// with the inotify kernel state which has already deleted the watch
// automatically.
if ok && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
delete(w.paths, int(raw.Wd))
delete(w.watches, name)
}
w.mu.Unlock()
if nameLen > 0 {
// Point "bytes" at the first byte of the filename
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
}
event := newEvent(name, mask)
// Send the events that are not ignored on the events channel
if !event.ignoreLinux(mask) {
select {
case w.Events <- event:
case <-w.done:
return
}
}
// Move to the next event in the buffer
offset += unix.SizeofInotifyEvent + nameLen
}
}
}
// Certain types of events can be "ignored" and not sent over the Events
// channel. Such as events marked ignore by the kernel, or MODIFY events
// against files that do not exist.
func (e *Event) ignoreLinux(mask uint32) bool {
// Ignore anything the inotify API says to ignore
if mask&unix.IN_IGNORED == unix.IN_IGNORED {
return true
}
// If the event is not a DELETE or RENAME, the file must exist.
// Otherwise the event is ignored.
// *Note*: this was put in place because it was seen that a MODIFY
// event was sent after the DELETE. This ignores that MODIFY and
// assumes a DELETE will come or has come if the file doesn't exist.
if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
_, statErr := os.Lstat(e.Name)
return os.IsNotExist(statErr)
}
return false
}
// newEvent returns an platform-independent Event based on an inotify mask.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
e.Op |= Create
}
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
e.Op |= Remove
}
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
e.Op |= Write
}
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
e.Op |= Rename
}
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
e.Op |= Chmod
}
return e
}

187
vendor/github.com/fsnotify/fsnotify/inotify_poller.go generated vendored Normal file
View File

@ -0,0 +1,187 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux
package fsnotify
import (
"errors"
"golang.org/x/sys/unix"
)
type fdPoller struct {
fd int // File descriptor (as returned by the inotify_init() syscall)
epfd int // Epoll file descriptor
pipe [2]int // Pipe for waking up
}
func emptyPoller(fd int) *fdPoller {
poller := new(fdPoller)
poller.fd = fd
poller.epfd = -1
poller.pipe[0] = -1
poller.pipe[1] = -1
return poller
}
// Create a new inotify poller.
// This creates an inotify handler, and an epoll handler.
func newFdPoller(fd int) (*fdPoller, error) {
var errno error
poller := emptyPoller(fd)
defer func() {
if errno != nil {
poller.close()
}
}()
poller.fd = fd
// Create epoll fd
poller.epfd, errno = unix.EpollCreate1(unix.EPOLL_CLOEXEC)
if poller.epfd == -1 {
return nil, errno
}
// Create pipe; pipe[0] is the read end, pipe[1] the write end.
errno = unix.Pipe2(poller.pipe[:], unix.O_NONBLOCK|unix.O_CLOEXEC)
if errno != nil {
return nil, errno
}
// Register inotify fd with epoll
event := unix.EpollEvent{
Fd: int32(poller.fd),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.fd, &event)
if errno != nil {
return nil, errno
}
// Register pipe fd with epoll
event = unix.EpollEvent{
Fd: int32(poller.pipe[0]),
Events: unix.EPOLLIN,
}
errno = unix.EpollCtl(poller.epfd, unix.EPOLL_CTL_ADD, poller.pipe[0], &event)
if errno != nil {
return nil, errno
}
return poller, nil
}
// Wait using epoll.
// Returns true if something is ready to be read,
// false if there is not.
func (poller *fdPoller) wait() (bool, error) {
// 3 possible events per fd, and 2 fds, makes a maximum of 6 events.
// I don't know whether epoll_wait returns the number of events returned,
// or the total number of events ready.
// I decided to catch both by making the buffer one larger than the maximum.
events := make([]unix.EpollEvent, 7)
for {
n, errno := unix.EpollWait(poller.epfd, events, -1)
if n == -1 {
if errno == unix.EINTR {
continue
}
return false, errno
}
if n == 0 {
// If there are no events, try again.
continue
}
if n > 6 {
// This should never happen. More events were returned than should be possible.
return false, errors.New("epoll_wait returned more events than I know what to do with")
}
ready := events[:n]
epollhup := false
epollerr := false
epollin := false
for _, event := range ready {
if event.Fd == int32(poller.fd) {
if event.Events&unix.EPOLLHUP != 0 {
// This should not happen, but if it does, treat it as a wakeup.
epollhup = true
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the file descriptor, we should pretend
// something is ready to read, and let unix.Read pick up the error.
epollerr = true
}
if event.Events&unix.EPOLLIN != 0 {
// There is data to read.
epollin = true
}
}
if event.Fd == int32(poller.pipe[0]) {
if event.Events&unix.EPOLLHUP != 0 {
// Write pipe descriptor was closed, by us. This means we're closing down the
// watcher, and we should wake up.
}
if event.Events&unix.EPOLLERR != 0 {
// If an error is waiting on the pipe file descriptor.
// This is an absolute mystery, and should never ever happen.
return false, errors.New("Error on the pipe descriptor.")
}
if event.Events&unix.EPOLLIN != 0 {
// This is a regular wakeup, so we have to clear the buffer.
err := poller.clearWake()
if err != nil {
return false, err
}
}
}
}
if epollhup || epollerr || epollin {
return true, nil
}
return false, nil
}
}
// Close the write end of the poller.
func (poller *fdPoller) wake() error {
buf := make([]byte, 1)
n, errno := unix.Write(poller.pipe[1], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is full, poller will wake.
return nil
}
return errno
}
return nil
}
func (poller *fdPoller) clearWake() error {
// You have to be woken up a LOT in order to get to 100!
buf := make([]byte, 100)
n, errno := unix.Read(poller.pipe[0], buf)
if n == -1 {
if errno == unix.EAGAIN {
// Buffer is empty, someone else cleared our wake.
return nil
}
return errno
}
return nil
}
// Close all poller file descriptors, but not the one passed to it.
func (poller *fdPoller) close() {
if poller.pipe[1] != -1 {
unix.Close(poller.pipe[1])
}
if poller.pipe[0] != -1 {
unix.Close(poller.pipe[0])
}
if poller.epfd != -1 {
unix.Close(poller.epfd)
}
}

521
vendor/github.com/fsnotify/fsnotify/kqueue.go generated vendored Normal file
View File

@ -0,0 +1,521 @@
// Copyright 2010 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly darwin
package fsnotify
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"
"golang.org/x/sys/unix"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
kq int // File descriptor (as returned by the kqueue() syscall).
mu sync.Mutex // Protects access to watcher data
watches map[string]int // Map of watched file descriptors (key: path).
externalWatches map[string]bool // Map of watches added by user of the library.
dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
isClosed bool // Set to true when Close() is first called
}
type pathInfo struct {
name string
isDir bool
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
kq, err := kqueue()
if err != nil {
return nil, err
}
w := &Watcher{
kq: kq,
watches: make(map[string]int),
dirFlags: make(map[string]uint32),
paths: make(map[int]pathInfo),
fileExists: make(map[string]bool),
externalWatches: make(map[string]bool),
Events: make(chan Event),
Errors: make(chan error),
done: make(chan struct{}),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return nil
}
w.isClosed = true
// copy paths to remove while locked
var pathsToRemove = make([]string, 0, len(w.watches))
for name := range w.watches {
pathsToRemove = append(pathsToRemove, name)
}
w.mu.Unlock()
// unlock before calling Remove, which also locks
for _, name := range pathsToRemove {
w.Remove(name)
}
// send a "quit" message to the reader goroutine
close(w.done)
return nil
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
w.mu.Lock()
w.externalWatches[name] = true
w.mu.Unlock()
_, err := w.addWatch(name, noteAllEvents)
return err
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
name = filepath.Clean(name)
w.mu.Lock()
watchfd, ok := w.watches[name]
w.mu.Unlock()
if !ok {
return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
}
const registerRemove = unix.EV_DELETE
if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
return err
}
unix.Close(watchfd)
w.mu.Lock()
isDir := w.paths[watchfd].isDir
delete(w.watches, name)
delete(w.paths, watchfd)
delete(w.dirFlags, name)
w.mu.Unlock()
// Find all watched paths that are in this directory that are not external.
if isDir {
var pathsToRemove []string
w.mu.Lock()
for _, path := range w.paths {
wdir, _ := filepath.Split(path.name)
if filepath.Clean(wdir) == name {
if !w.externalWatches[path.name] {
pathsToRemove = append(pathsToRemove, path.name)
}
}
}
w.mu.Unlock()
for _, name := range pathsToRemove {
// Since these are internal, not much sense in propagating error
// to the user, as that will just confuse them with an error about
// a path they did not explicitly watch themselves.
w.Remove(name)
}
}
return nil
}
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
// keventWaitTime to block on each read from kevent
var keventWaitTime = durationToTimespec(100 * time.Millisecond)
// addWatch adds name to the watched file set.
// The flags are interpreted as described in kevent(2).
// Returns the real path to the file which was added, if any, which may be different from the one passed in the case of symlinks.
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
var isDir bool
// Make ./name and name equivalent
name = filepath.Clean(name)
w.mu.Lock()
if w.isClosed {
w.mu.Unlock()
return "", errors.New("kevent instance already closed")
}
watchfd, alreadyWatching := w.watches[name]
// We already have a watch, but we can still override flags.
if alreadyWatching {
isDir = w.paths[watchfd].isDir
}
w.mu.Unlock()
if !alreadyWatching {
fi, err := os.Lstat(name)
if err != nil {
return "", err
}
// Don't watch sockets.
if fi.Mode()&os.ModeSocket == os.ModeSocket {
return "", nil
}
// Don't watch named pipes.
if fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe {
return "", nil
}
// Follow Symlinks
// Unfortunately, Linux can add bogus symlinks to watch list without
// issue, and Windows can't do symlinks period (AFAIK). To maintain
// consistency, we will act like everything is fine. There will simply
// be no file events for broken symlinks.
// Hence the returns of nil on errors.
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
name, err = filepath.EvalSymlinks(name)
if err != nil {
return "", nil
}
w.mu.Lock()
_, alreadyWatching = w.watches[name]
w.mu.Unlock()
if alreadyWatching {
return name, nil
}
fi, err = os.Lstat(name)
if err != nil {
return "", nil
}
}
watchfd, err = unix.Open(name, openMode, 0700)
if watchfd == -1 {
return "", err
}
isDir = fi.IsDir()
}
const registerAdd = unix.EV_ADD | unix.EV_CLEAR | unix.EV_ENABLE
if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
unix.Close(watchfd)
return "", err
}
if !alreadyWatching {
w.mu.Lock()
w.watches[name] = watchfd
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
w.mu.Unlock()
}
if isDir {
// Watch the directory if it has not been watched before,
// or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
w.mu.Lock()
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
// Store flags so this watch can be updated later
w.dirFlags[name] = flags
w.mu.Unlock()
if watchDir {
if err := w.watchDirectoryFiles(name); err != nil {
return "", err
}
}
}
return name, nil
}
// readEvents reads from kqueue and converts the received kevents into
// Event values that it sends down the Events channel.
func (w *Watcher) readEvents() {
eventBuffer := make([]unix.Kevent_t, 10)
loop:
for {
// See if there is a message on the "done" channel
select {
case <-w.done:
break loop
default:
}
// Get new events
kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
// EINTR is okay, the syscall was interrupted before timeout expired.
if err != nil && err != unix.EINTR {
select {
case w.Errors <- err:
case <-w.done:
break loop
}
continue
}
// Flush the events we received to the Events channel
for len(kevents) > 0 {
kevent := &kevents[0]
watchfd := int(kevent.Ident)
mask := uint32(kevent.Fflags)
w.mu.Lock()
path := w.paths[watchfd]
w.mu.Unlock()
event := newEvent(path.name, mask)
if path.isDir && !(event.Op&Remove == Remove) {
// Double check to make sure the directory exists. This can happen when
// we do a rm -fr on a recursively watched folders and we receive a
// modification event first but the folder has been deleted and later
// receive the delete event
if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
// mark is as delete event
event.Op |= Remove
}
}
if event.Op&Rename == Rename || event.Op&Remove == Remove {
w.Remove(event.Name)
w.mu.Lock()
delete(w.fileExists, event.Name)
w.mu.Unlock()
}
if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
w.sendDirectoryChangeEvents(event.Name)
} else {
// Send the event on the Events channel.
select {
case w.Events <- event:
case <-w.done:
break loop
}
}
if event.Op&Remove == Remove {
// Look for a file that may have overwritten this.
// For example, mv f1 f2 will delete f2, then create f2.
if path.isDir {
fileDir := filepath.Clean(event.Name)
w.mu.Lock()
_, found := w.watches[fileDir]
w.mu.Unlock()
if found {
// make sure the directory exists before we watch for changes. When we
// do a recursive watch and perform rm -fr, the parent directory might
// have gone missing, ignore the missing directory and let the
// upcoming delete event remove the watch from the parent directory.
if _, err := os.Lstat(fileDir); err == nil {
w.sendDirectoryChangeEvents(fileDir)
}
}
} else {
filePath := filepath.Clean(event.Name)
if fileInfo, err := os.Lstat(filePath); err == nil {
w.sendFileCreatedEventIfNew(filePath, fileInfo)
}
}
}
// Move to next event
kevents = kevents[1:]
}
}
// cleanup
err := unix.Close(w.kq)
if err != nil {
// only way the previous loop breaks is if w.done was closed so we need to async send to w.Errors.
select {
case w.Errors <- err:
default:
}
}
close(w.Events)
close(w.Errors)
}
// newEvent returns an platform-independent Event based on kqueue Fflags.
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
e.Op |= Remove
}
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
e.Op |= Write
}
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
e.Op |= Rename
}
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
e.Op |= Chmod
}
return e
}
func newCreateEvent(name string) Event {
return Event{Name: name, Op: Create}
}
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
return err
}
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
}
return nil
}
// sendDirectoryEvents searches the directory for newly created files
// and sends them over the event channel. This functionality is to have
// the BSD version of fsnotify match Linux inotify which provides a
// create event for files created in a watched directory.
func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
// Get all files
files, err := ioutil.ReadDir(dirPath)
if err != nil {
select {
case w.Errors <- err:
case <-w.done:
return
}
}
// Search for new files
for _, fileInfo := range files {
filePath := filepath.Join(dirPath, fileInfo.Name())
err := w.sendFileCreatedEventIfNew(filePath, fileInfo)
if err != nil {
return
}
}
}
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fileInfo os.FileInfo) (err error) {
w.mu.Lock()
_, doesExist := w.fileExists[filePath]
w.mu.Unlock()
if !doesExist {
// Send create event
select {
case w.Events <- newCreateEvent(filePath):
case <-w.done:
return
}
}
// like watchDirectoryFiles (but without doing another ReadDir)
filePath, err = w.internalWatch(filePath, fileInfo)
if err != nil {
return err
}
w.mu.Lock()
w.fileExists[filePath] = true
w.mu.Unlock()
return nil
}
func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) (string, error) {
if fileInfo.IsDir() {
// mimic Linux providing delete events for subdirectories
// but preserve the flags used if currently watching subdirectory
w.mu.Lock()
flags := w.dirFlags[name]
w.mu.Unlock()
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
return w.addWatch(name, flags)
}
// watch file to mimic Linux inotify
return w.addWatch(name, noteAllEvents)
}
// kqueue creates a new kernel event queue and returns a descriptor.
func kqueue() (kq int, err error) {
kq, err = unix.Kqueue()
if kq == -1 {
return kq, err
}
return kq, nil
}
// register events with the queue
func register(kq int, fds []int, flags int, fflags uint32) error {
changes := make([]unix.Kevent_t, len(fds))
for i, fd := range fds {
// SetKevent converts int to the platform-specific types:
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
changes[i].Fflags = fflags
}
// register the events
success, err := unix.Kevent(kq, changes, nil, nil)
if success == -1 {
return err
}
return nil
}
// read retrieves pending events, or waits until an event occurs.
// A timeout of nil blocks indefinitely, while 0 polls the queue.
func read(kq int, events []unix.Kevent_t, timeout *unix.Timespec) ([]unix.Kevent_t, error) {
n, err := unix.Kevent(kq, nil, events, timeout)
if err != nil {
return nil, err
}
return events[0:n], nil
}
// durationToTimespec prepares a timeout value
func durationToTimespec(d time.Duration) unix.Timespec {
return unix.NsecToTimespec(d.Nanoseconds())
}

11
vendor/github.com/fsnotify/fsnotify/open_mode_bsd.go generated vendored Normal file
View File

@ -0,0 +1,11 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build freebsd openbsd netbsd dragonfly
package fsnotify
import "golang.org/x/sys/unix"
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC

View File

@ -0,0 +1,12 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build darwin
package fsnotify
import "golang.org/x/sys/unix"
// note: this constant is not defined on BSD
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC

561
vendor/github.com/fsnotify/fsnotify/windows.go generated vendored Normal file
View File

@ -0,0 +1,561 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build windows
package fsnotify
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"sync"
"syscall"
"unsafe"
)
// Watcher watches a set of files, delivering events to a channel.
type Watcher struct {
Events chan Event
Errors chan error
isClosed bool // Set to true when Close() is first called
mu sync.Mutex // Map access
port syscall.Handle // Handle to completion port
watches watchMap // Map of watches (key: i-number)
input chan *input // Inputs to the reader are sent on this channel
quit chan chan<- error
}
// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
func NewWatcher() (*Watcher, error) {
port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
if e != nil {
return nil, os.NewSyscallError("CreateIoCompletionPort", e)
}
w := &Watcher{
port: port,
watches: make(watchMap),
input: make(chan *input, 1),
Events: make(chan Event, 50),
Errors: make(chan error),
quit: make(chan chan<- error, 1),
}
go w.readEvents()
return w, nil
}
// Close removes all watches and closes the events channel.
func (w *Watcher) Close() error {
if w.isClosed {
return nil
}
w.isClosed = true
// Send "quit" message to the reader goroutine
ch := make(chan error)
w.quit <- ch
if err := w.wakeupReader(); err != nil {
return err
}
return <-ch
}
// Add starts watching the named file or directory (non-recursively).
func (w *Watcher) Add(name string) error {
if w.isClosed {
return errors.New("watcher already closed")
}
in := &input{
op: opAddWatch,
path: filepath.Clean(name),
flags: sysFSALLEVENTS,
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
// Remove stops watching the the named file or directory (non-recursively).
func (w *Watcher) Remove(name string) error {
in := &input{
op: opRemoveWatch,
path: filepath.Clean(name),
reply: make(chan error),
}
w.input <- in
if err := w.wakeupReader(); err != nil {
return err
}
return <-in.reply
}
const (
// Options for AddWatch
sysFSONESHOT = 0x80000000
sysFSONLYDIR = 0x1000000
// Events
sysFSACCESS = 0x1
sysFSALLEVENTS = 0xfff
sysFSATTRIB = 0x4
sysFSCLOSE = 0x18
sysFSCREATE = 0x100
sysFSDELETE = 0x200
sysFSDELETESELF = 0x400
sysFSMODIFY = 0x2
sysFSMOVE = 0xc0
sysFSMOVEDFROM = 0x40
sysFSMOVEDTO = 0x80
sysFSMOVESELF = 0x800
// Special events
sysFSIGNORED = 0x8000
sysFSQOVERFLOW = 0x4000
)
func newEvent(name string, mask uint32) Event {
e := Event{Name: name}
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
e.Op |= Create
}
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
e.Op |= Remove
}
if mask&sysFSMODIFY == sysFSMODIFY {
e.Op |= Write
}
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
e.Op |= Rename
}
if mask&sysFSATTRIB == sysFSATTRIB {
e.Op |= Chmod
}
return e
}
const (
opAddWatch = iota
opRemoveWatch
)
const (
provisional uint64 = 1 << (32 + iota)
)
type input struct {
op int
path string
flags uint32
reply chan error
}
type inode struct {
handle syscall.Handle
volume uint32
index uint64
}
type watch struct {
ov syscall.Overlapped
ino *inode // i-number
path string // Directory path
mask uint64 // Directory itself is being watched with these notify flags
names map[string]uint64 // Map of names being watched and their notify flags
rename string // Remembers the old name while renaming a file
buf [4096]byte
}
type indexMap map[uint64]*watch
type watchMap map[uint32]indexMap
func (w *Watcher) wakeupReader() error {
e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
if e != nil {
return os.NewSyscallError("PostQueuedCompletionStatus", e)
}
return nil
}
func getDir(pathname string) (dir string, err error) {
attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
if e != nil {
return "", os.NewSyscallError("GetFileAttributes", e)
}
if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
dir = pathname
} else {
dir, _ = filepath.Split(pathname)
dir = filepath.Clean(dir)
}
return
}
func getIno(path string) (ino *inode, err error) {
h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
syscall.FILE_LIST_DIRECTORY,
syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
nil, syscall.OPEN_EXISTING,
syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
if e != nil {
return nil, os.NewSyscallError("CreateFile", e)
}
var fi syscall.ByHandleFileInformation
if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
syscall.CloseHandle(h)
return nil, os.NewSyscallError("GetFileInformationByHandle", e)
}
ino = &inode{
handle: h,
volume: fi.VolumeSerialNumber,
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
}
return ino, nil
}
// Must run within the I/O thread.
func (m watchMap) get(ino *inode) *watch {
if i := m[ino.volume]; i != nil {
return i[ino.index]
}
return nil
}
// Must run within the I/O thread.
func (m watchMap) set(ino *inode, watch *watch) {
i := m[ino.volume]
if i == nil {
i = make(indexMap)
m[ino.volume] = i
}
i[ino.index] = watch
}
// Must run within the I/O thread.
func (w *Watcher) addWatch(pathname string, flags uint64) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
if flags&sysFSONLYDIR != 0 && pathname != dir {
return nil
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watchEntry := w.watches.get(ino)
w.mu.Unlock()
if watchEntry == nil {
if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
syscall.CloseHandle(ino.handle)
return os.NewSyscallError("CreateIoCompletionPort", e)
}
watchEntry = &watch{
ino: ino,
path: dir,
names: make(map[string]uint64),
}
w.mu.Lock()
w.watches.set(ino, watchEntry)
w.mu.Unlock()
flags |= provisional
} else {
syscall.CloseHandle(ino.handle)
}
if pathname == dir {
watchEntry.mask |= flags
} else {
watchEntry.names[filepath.Base(pathname)] |= flags
}
if err = w.startRead(watchEntry); err != nil {
return err
}
if pathname == dir {
watchEntry.mask &= ^provisional
} else {
watchEntry.names[filepath.Base(pathname)] &= ^provisional
}
return nil
}
// Must run within the I/O thread.
func (w *Watcher) remWatch(pathname string) error {
dir, err := getDir(pathname)
if err != nil {
return err
}
ino, err := getIno(dir)
if err != nil {
return err
}
w.mu.Lock()
watch := w.watches.get(ino)
w.mu.Unlock()
if watch == nil {
return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
}
if pathname == dir {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
watch.mask = 0
} else {
name := filepath.Base(pathname)
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
return w.startRead(watch)
}
// Must run within the I/O thread.
func (w *Watcher) deleteWatch(watch *watch) {
for name, mask := range watch.names {
if mask&provisional == 0 {
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
}
delete(watch.names, name)
}
if watch.mask != 0 {
if watch.mask&provisional == 0 {
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
}
watch.mask = 0
}
}
// Must run within the I/O thread.
func (w *Watcher) startRead(watch *watch) error {
if e := syscall.CancelIo(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CancelIo", e)
w.deleteWatch(watch)
}
mask := toWindowsFlags(watch.mask)
for _, m := range watch.names {
mask |= toWindowsFlags(m)
}
if mask == 0 {
if e := syscall.CloseHandle(watch.ino.handle); e != nil {
w.Errors <- os.NewSyscallError("CloseHandle", e)
}
w.mu.Lock()
delete(w.watches[watch.ino.volume], watch.ino.index)
w.mu.Unlock()
return nil
}
e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
if e != nil {
err := os.NewSyscallError("ReadDirectoryChanges", e)
if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
// Watched directory was probably removed
if w.sendEvent(watch.path, watch.mask&sysFSDELETESELF) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
err = nil
}
w.deleteWatch(watch)
w.startRead(watch)
return err
}
return nil
}
// readEvents reads from the I/O completion port, converts the
// received events into Event objects and sends them via the Events channel.
// Entry point to the I/O thread.
func (w *Watcher) readEvents() {
var (
n, key uint32
ov *syscall.Overlapped
)
runtime.LockOSThread()
for {
e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
watch := (*watch)(unsafe.Pointer(ov))
if watch == nil {
select {
case ch := <-w.quit:
w.mu.Lock()
var indexes []indexMap
for _, index := range w.watches {
indexes = append(indexes, index)
}
w.mu.Unlock()
for _, index := range indexes {
for _, watch := range index {
w.deleteWatch(watch)
w.startRead(watch)
}
}
var err error
if e := syscall.CloseHandle(w.port); e != nil {
err = os.NewSyscallError("CloseHandle", e)
}
close(w.Events)
close(w.Errors)
ch <- err
return
case in := <-w.input:
switch in.op {
case opAddWatch:
in.reply <- w.addWatch(in.path, uint64(in.flags))
case opRemoveWatch:
in.reply <- w.remWatch(in.path)
}
default:
}
continue
}
switch e {
case syscall.ERROR_MORE_DATA:
if watch == nil {
w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
} else {
// The i/o succeeded but the buffer is full.
// In theory we should be building up a full packet.
// In practice we can get away with just carrying on.
n = uint32(unsafe.Sizeof(watch.buf))
}
case syscall.ERROR_ACCESS_DENIED:
// Watched directory was probably removed
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
w.deleteWatch(watch)
w.startRead(watch)
continue
case syscall.ERROR_OPERATION_ABORTED:
// CancelIo was called on this handle
continue
default:
w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
continue
case nil:
}
var offset uint32
for {
if n == 0 {
w.Events <- newEvent("", sysFSQOVERFLOW)
w.Errors <- errors.New("short read in readEvents()")
break
}
// Point "raw" to the event in the buffer
raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
fullname := filepath.Join(watch.path, name)
var mask uint64
switch raw.Action {
case syscall.FILE_ACTION_REMOVED:
mask = sysFSDELETESELF
case syscall.FILE_ACTION_MODIFIED:
mask = sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
watch.rename = name
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
if watch.names[watch.rename] != 0 {
watch.names[name] |= watch.names[watch.rename]
delete(watch.names, watch.rename)
mask = sysFSMOVESELF
}
}
sendNameEvent := func() {
if w.sendEvent(fullname, watch.names[name]&mask) {
if watch.names[name]&sysFSONESHOT != 0 {
delete(watch.names, name)
}
}
}
if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
sendNameEvent()
}
if raw.Action == syscall.FILE_ACTION_REMOVED {
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
delete(watch.names, name)
}
if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
if watch.mask&sysFSONESHOT != 0 {
watch.mask = 0
}
}
if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
fullname = filepath.Join(watch.path, watch.rename)
sendNameEvent()
}
// Move to the next event in the buffer
if raw.NextEntryOffset == 0 {
break
}
offset += raw.NextEntryOffset
// Error!
if offset >= n {
w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
break
}
}
if err := w.startRead(watch); err != nil {
w.Errors <- err
}
}
}
func (w *Watcher) sendEvent(name string, mask uint64) bool {
if mask == 0 {
return false
}
event := newEvent(name, uint32(mask))
select {
case ch := <-w.quit:
w.quit <- ch
case w.Events <- event:
}
return true
}
func toWindowsFlags(mask uint64) uint32 {
var m uint32
if mask&sysFSACCESS != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
}
if mask&sysFSMODIFY != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
}
if mask&sysFSATTRIB != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
}
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
}
return m
}
func toFSnotifyFlags(action uint32) uint64 {
switch action {
case syscall.FILE_ACTION_ADDED:
return sysFSCREATE
case syscall.FILE_ACTION_REMOVED:
return sysFSDELETE
case syscall.FILE_ACTION_MODIFIED:
return sysFSMODIFY
case syscall.FILE_ACTION_RENAMED_OLD_NAME:
return sysFSMOVEDFROM
case syscall.FILE_ACTION_RENAMED_NEW_NAME:
return sysFSMOVEDTO
}
return 0
}

28
vendor/github.com/guillermo/go.procmeminfo/README.md generated vendored Normal file
View File

@ -0,0 +1,28 @@
# procmeminfo
[![GoDoc](http://godoc.org/github.com/guillermo/go.procmeminfo?status.png)](http://godoc.org/github.com/guillermo/go.procmeminfo)
Package procmeminfo provides an interface for /proc/meminfo
```golang
import "github.com/guillermo/go.procmeminfo"
meminfo := &procmeminfo.MemInfo{}
meminfo.Update()
(*meminfo)['Cached'] // Get cached memory
(*meminfo)['Buffers'] // Get buffers size
(*meminfo)['...'] // Any field in /proc/meminfo
meminfo.Total() // Total memory size in bytes
meminfo.Free() // Free Memory (Free + Cached + Buffers)
meminfo.Used() // Total - Used
```
## Docs
Visit: http://godoc.org/github.com/guillermo/go.procmeminfo
## LICENSE
BSD

307
vendor/github.com/guillermo/go.procmeminfo/meminfo.go generated vendored Normal file
View File

@ -0,0 +1,307 @@
/*
Package procmeminfo provides an interface for /proc/meminfo
import "github.com/guillermo/go.procmeminfo"
meminfo := &procmeminfo.MemInfo{}
meminfo.Update()
Once the info was updated you can access like a normal map[string]float64
v := (*meminfo)["MemTotal"] // 1809379328 (1766972 * 1024)
It also implement some handy methods, like:
meminfo.Total() // (*meminfo)["MemTotal"]
meminfo.Free() // MemFree + Buffers + Cached
meminfo.Used() // Total - Free
Return all the values in units, so while you get this from cat /proc/meminfo
MemTotal: 1766972 kB
MemFree: 115752 kB
Buffers: 3172 kB
Cached: 182552 kB
SwapCached: 83572 kB
Active: 1055284 kB
Inactive: 382872 kB
Active(anon): 932712 kB
Inactive(anon): 329508 kB
Active(file): 122572 kB
Inactive(file): 53364 kB
Unevictable: 10640 kB
Mlocked: 10640 kB
SwapTotal: 1808668 kB
SwapFree: 1205672 kB
Dirty: 100 kB
Writeback: 0 kB
AnonPages: 1214740 kB
Mapped: 115636 kB
Shmem: 4840 kB
Slab: 77412 kB
SReclaimable: 34344 kB
SUnreclaim: 43068 kB
KernelStack: 4328 kB
PageTables: 39428 kB
NFS_Unstable: 0 kB
Bounce: 0 kB
WritebackTmp: 0 kB
CommitLimit: 2692152 kB
Committed_AS: 5448372 kB
VmallocTotal: 34359738367 kB
VmallocUsed: 106636 kB
VmallocChunk: 34359618556 kB
HardwareCorrupted: 0 kB
AnonHugePages: 0 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
DirectMap4k: 216236 kB
DirectMap2M: 1593344 kB
All the kB values are multiply by 1024
This is the info extracted from the man page of proc:
/proc/meminfo
This file reports statistics about memory usage on the system. It is used by free(1) to report the amount of free and used memory (both physical and swap) on the system as well as the shared memory and buffers used by the kernel. Each line of the file consists of a parameter name, followed by a colon, the value of
the parameter, and an option unit of measurement (e.g., "kB"). The list below describes the parameter names and the format specifier required to read the field value. Except as noted below, all of the fields have been present since at least Linux 2.6.0. Some fileds are displayed only if the kernel was configured
with various options; those dependencies are noted in the list.
MemTotal %lu
Total usable RAM (i.e. physical RAM minus a few reserved bits and the kernel binary code).
MemFree %lu
The sum of LowFree+HighFree.
Buffers %lu
Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).
Cached %lu
In-memory cache for files read from the disk (the page cache). Doesn't include SwapCached.
SwapCached %lu
Memory that once was swapped out, is swapped back in but still also is in the swap file. (If memory pressure is high, these pages don't need to be swapped out again because they are already in the swap file. This saves I/O.)
Active %lu
Memory that has been used more recently and usually not reclaimed unless absolutely necessary.
Inactive %lu
Memory which has been less recently used. It is more eligible to be reclaimed for other purposes.
Active(anon) %lu (since Linux 2.6.28)
[To be documented.]
Inactive(anon) %lu (since Linux 2.6.28)
[To be documented.]
Active(file) %lu (since Linux 2.6.28)
[To be documented.]
Inactive(file) %lu (since Linux 2.6.28)
[To be documented.]
Unevictable %lu (since Linux 2.6.28)
(From Linux 2.6.28 to 2.6.30, CONFIG_UNEVICTABLE_LRU was required.) [To be documented.]
Mlocked %lu (since Linux 2.6.28)
(From Linux 2.6.28 to 2.6.30, CONFIG_UNEVICTABLE_LRU was required.) [To be documented.]
HighTotal %lu
(Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) Total amount of highmem. Highmem is all memory above ~860MB of physical memory. Highmem areas are for use by user-space programs, or for the page cache. The kernel must use tricks to access this memory, making it slower to access than lowmem.
HighFree %lu
(Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) Amount of free highmem.
LowTotal %lu
(Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) Total amount of lowmem. Lowmem is memory which can be used for everything that highmem can be used for, but it is also available for the kernel's use for its own data structures. Among many other things, it is where everything from Slab is allo
cated. Bad things happen when you're out of lowmem.
LowFree %lu
(Starting with Linux 2.6.19, CONFIG_HIGHMEM is required.) Amount of free lowmem.
MmapCopy %lu (since Linux 2.6.29)
(CONFIG_MMU is required.) [To be documented.]
SwapTotal %lu
Total amount of swap space available.
SwapFree %lu
Amount of swap space that is currently unused.
Dirty %lu
Memory which is waiting to get written back to the disk.
Writeback %lu
Memory which is actively being written back to the disk.
AnonPages %lu (since Linux 2.6.18)
Non-file backed pages mapped into user-space page tables.
Mapped %lu
Files which have been mmaped, such as libraries.
Shmem %lu (since Linux 2.6.32)
[To be documented.]
Slab %lu
In-kernel data structures cache.
SReclaimable %lu (since Linux 2.6.19)
Part of Slab, that might be reclaimed, such as caches.
SUnreclaim %lu (since Linux 2.6.19)
Part of Slab, that cannot be reclaimed on memory pressure.
KernelStack %lu (since Linux 2.6.32)
Amount of memory allocated to kernel stacks.
PageTables %lu (since Linux 2.6.18)
Amount of memory dedicated to the lowest level of page tables.
Quicklists %lu (since Linux 2.6.27)
(CONFIG_QUICKLIST is required.) [To be documented.]
NFS_Unstable %lu (since Linux 2.6.18)
NFS pages sent to the server, but not yet committed to stable storage.
Bounce %lu (since Linux 2.6.18)
Memory used for block device "bounce buffers".
WritebackTmp %lu (since Linux 2.6.26)
Memory used by FUSE for temporary writeback buffers.
CommitLimit %lu (since Linux 2.6.10)
Based on the overcommit ratio ('vm.overcommit_ratio'), this is the total amount of memory currently available to be allocated on the system. This limit is adhered to only if strict overcommit accounting is enabled (mode 2 in /proc/sys/vm/overcommit_ratio). The CommitLimit is calculated using the following
formula:
CommitLimit = (overcommit_ratio * Physical RAM) + Swap
For example, on a system with 1GB of physical RAM and 7GB of swap with a overcommit_ratio of 30, this formula yields a CommitLimit of 7.3GB. For more details, see the memory overcommit documentation in the kernel source file Documentation/vm/overcommit-accounting.
Committed_AS %lu
The amount of memory presently allocated on the system. The committed memory is a sum of all of the memory which has been allocated by processes, even if it has not been "used" by them as of yet. A process which allocates 1GB of memory (using malloc(3) or similar), but touches only 300MB of that memory will
show up as using only 300MB of memory even if it has the address space allocated for the entire 1GB. This 1GB is memory which has been "committed" to by the VM and can be used at any time by the allocating application. With strict overcommit enabled on the system (mode 2 /proc/sys/vm/overcommit_memory),
allocations which would exceed the CommitLimit (detailed above) will not be permitted. This is useful if one needs to guarantee that processes will not fail due to lack of memory once that memory has been successfully allocated.
VmallocTotal %lu
Total size of vmalloc memory area.
VmallocUsed %lu
Amount of vmalloc area which is used.
VmallocChunk %lu
Largest contiguous block of vmalloc area which is free.
HardwareCorrupted %lu (since Linux 2.6.32)
(CONFIG_MEMORY_FAILURE is required.) [To be documented.]
AnonHugePages %lu (since Linux 2.6.38)
(CONFIG_TRANSPARENT_HUGEPAGE is required.) Non-file backed huge pages mapped into user-space page tables.
HugePages_Total %lu
(CONFIG_HUGETLB_PAGE is required.) The size of the pool of huge pages.
HugePages_Free %lu
(CONFIG_HUGETLB_PAGE is required.) The number of huge pages in the pool that are not yet allocated.
HugePages_Rsvd %lu (since Linux 2.6.17)
(CONFIG_HUGETLB_PAGE is required.) This is the number of huge pages for which a commitment to allocate from the pool has been made, but no allocation has yet been made. These reserved huge pages guarantee that an application will be able to allocate a huge page from the pool of huge pages at fault time.
HugePages_Surp %lu (since Linux 2.6.24)
(CONFIG_HUGETLB_PAGE is required.) This is the number of huge pages in the pool above the value in /proc/sys/vm/nr_hugepages. The maximum number of surplus huge pages is controlled by /proc/sys/vm/nr_overcommit_hugepages.
Hugepagesize %lu
(CONFIG_HUGETLB_PAGE is required.) The size of huge pages.
*/
package procmeminfo
import (
"bufio"
"os"
"path/filepath"
"strconv"
"strings"
)
// MemInfo is a map[string]uint64 with all the values found in /proc/meminfo
type MemInfo map[string]uint64
// Update s with current values, usign the pid stored in the Stat
func (m *MemInfo) Update() error {
var err error
path := filepath.Join("/proc/meminfo")
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
text := scanner.Text()
n := strings.Index(text, ":")
if n == -1 {
continue
}
key := text[:n] // metric
data := strings.Split(strings.Trim(text[(n+1):], " "), " ")
if len(data) == 1 {
value, err := strconv.ParseUint(data[0], 10, 64)
if err != nil {
continue
}
(*m)[key] = value
} else if len(data) == 2 {
if data[1] == "kB" {
value, err := strconv.ParseUint(data[0], 10, 64)
if err != nil {
continue
}
(*m)[key] = value * 1024
}
}
}
return nil
}
// Total return the size of the memory in bytes.
// It is an alias of (*m)["MemInfo"]
func (m *MemInfo) Total() uint64 {
return (*m)["MemTotal"]
}
// Available return the available memory following this formula:
//
// Available = Free + Buffers + Cached
func (m *MemInfo) Available() uint64 {
d := *m
return d["MemFree"] + d["Buffers"] + d["Cached"]
}
// Used is a generic way of reporting used memory. It follows the next formula:
//
// Used = Total - Available
func (m *MemInfo) Used() uint64 {
return m.Total() - m.Available()
}
// Swap returns the % of swap used
func (m *MemInfo) Swap() int {
total := (*m)["SwapTotal"]
free := (*m)["SwapFree"]
if total == 0 {
return 0
}
return int((100 * (total - free)) / total)
}

1
vendor/github.com/knadh/koanf/.gitignore generated vendored Normal file
View File

@ -0,0 +1 @@
.env

17
vendor/github.com/knadh/koanf/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,17 @@
go_import_path: github.com/knadh/koanf
language: go
env:
global:
- GO111MODULE="on"
os:
- linux
go:
- 1.11.x
- 1.12.x
install: go get -v ./...
script: go test -v -cover ./...

21
vendor/github.com/knadh/koanf/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2019, Kailash Nadh. https://github.com/knadh
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

594
vendor/github.com/knadh/koanf/README.md generated vendored Normal file
View File

@ -0,0 +1,594 @@
![koanf](https://user-images.githubusercontent.com/547147/72681838-6981dd00-3aed-11ea-8f5d-310816c70c08.png)
**koanf** (pronounced _conf_; a play on the Japanese _Koan_) is a library for reading configuration from different sources in different formats in Go applications. It is a cleaner, lighter [alternative to spf13/viper](#alternative-to-viper) with better abstractions and extensibility and fewer dependencies.
koanf comes with built in support for reading configuration from files, command line flags, and environment variables, and can parse JSON, YAML, TOML, and Hashicorp HCL.
[![Build Status](https://travis-ci.com/knadh/koanf.svg?branch=master)](https://travis-ci.com/knadh/koanf) [![GoDoc](https://godoc.org/github.com/knadh/koanf?status.svg)](https://godoc.org/github.com/knadh/koanf)
### Installation
`go get -u github.com/knadh/koanf`
### Contents
- [Concepts](#concepts)
- [Reading config from files](#reading-config-from-files)
- [Watching files for changes](#watching-files-for-changes)
- [Reading from command line](#reading-from-command-line)
- [Reading environment variables](#reading-environment-variables)
- [Reading raw bytes](#reading-raw-bytes)
- [Unmarshalling and marshalling](#unmarshalling-and-marshalling)
- [Unmarshalling with flat paths](#unmarshalling-with-flat-paths)
- [Setting default values](#setting-default-values)
- [Order of merge and key case senstivity](#order-of-merge-and-key-case-senstivity)
- [Custom Providers and Parsers](#custom-providers-and-parsers)
- [API](#api)
### Concepts
- `koanf.Provider` is a generic interface that provides configuration, for example, from files, environment variables, HTTP sources, or anywhere. The configuration can either be raw bytes that a parser can parse, or it can be a nested map[string]interface{} that can be directly loaded.
- `koanf.Parser` is a generic interface that takes raw bytes, parses, and returns a nested map[string]interface{} representation. For example, JSON and YAML parsers.
- Once loaded into koanf, configuration are values queried by a delimited key path syntax. eg: `app.server.port`. Any delimiter can be chosen.
- Configuration from multiple sources can be loaded and merged into a koanf instance, for example, load from a file first and override certain values with flags from the command line.
With these two interface implementations, koanf can obtain a configuration from multiple sources and parse any format and make it available to an application.
### Reading config from files
```go
package main
import (
"fmt"
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
func main() {
// Load JSON config.
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// Load YAML config and merge into the previously loaded config (because we can).
k.Load(file.Provider("mock/mock.yaml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
}
```
### Watching files for changes
The `koanf.Provider` interface has a `Watch(cb)` method that asks a provider
to watch for changes and trigger the given callback that can live reload the
configuration.
Currently, `file.Provider` supports this.
```go
package main
import (
"fmt"
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
func main() {
// Load JSON config.
f := file.Provider("mock/mock.json")
if err := k.Load(f, json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// Load YAML config and merge into the previously loaded config (because we can).
k.Load(file.Provider("mock/mock.yaml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
// Watch the file and get a callback on change. The callback can do whatever,
// like re-load the configuration.
// File provider always returns a nil `event`.
f.Watch(func(event interface{}, err error) {
if err != nil {
log.Printf("watch error: %v", err)
return
}
log.Println("config changed. Reloading ...")
k.Load(f, json.Parser())
k.Print()
})
// Block forever (and manually make a change to mock/mock.json) to
// reload the config.
log.Println("waiting forever. Try making a change to mock/mock.json to live reload")
<-make(chan bool)
}
```
### Reading from command line
The following example shows the use of `posflag.Provider`, a wrapper over [spf13/pflag](/spf13/pflag) library, an advanced commandline lib. For Go's built in `flag` package, use `basicflag.Provider`.
```go
package main
import (
"fmt"
"log"
"os"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
flag "github.com/spf13/pflag"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
func main() {
// Use the POSIX compliant pflag lib instead of Go's flag lib.
f := flag.NewFlagSet("config", flag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
// Path to one or more config files to load into koanf along with some config params.
f.StringSlice("conf", []string{"mock/mock.toml"}, "path to one or more .toml config files")
f.String("time", "2020-01-01", "a time string")
f.String("type", "xxx", "type of the app")
f.Parse(os.Args[1:])
// Load the config files provided in the commandline.
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := k.Load(file.Provider(c), toml.Parser()); err != nil {
log.Fatalf("error loading file: %v", err)
}
}
// "time" and "type" may have been loaded from the config file, but
// they can still be overridden with the values from the command line.
// The bundled posflag.Provider takes a flagset from the spf13/pflag lib.
// Passing the Koanf instance to posflag helps it deal with default command
// line flag values that are not present in conf maps from previously loaded
// providers.
if err := k.Load(posflag.Provider(f, ".", k), nil); err != nil {
log.Fatalf("error loading config: %v", err)
}
fmt.Println("time is = ", k.String("time"))
}
```
### Reading environment variables
```go
package main
import (
"fmt"
"log"
"strings"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf.New(".")
func main() {
// Load JSON config.
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// Load environment variables and merge into the loaded config.
// "MYVAR" is the prefix to filter the env vars by.
// "." is the delimiter used to represent the key hierarchy in env vars.
// The (optional, or can be nil) function can be used to transform
// the env var names, for instance, to lowercase them.
//
// For example, env vars: MYVAR_TYPE and MYVAR_PARENT1_CHILD1_NAME
// will be merged into the "type" and the nested "parent1.child1.name"
// keys in the config file here as we lowercase the key,
// replace `_` with `.` and strip the MYVAR_ prefix so that
// only "parent1.child1.name" remains.
k.Load(env.Provider("MYVAR_", ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, "MYVAR_")), "_", ".", -1)
}), nil)
fmt.Println("name is = ", k.String("parent1.child1.name"))
}
```
### Reading from an S3 bucket
```go
// Load JSON config from s3.
if err := k.Load(s3.Provider(s3.Config{
AccessKey: os.Getenv("AWS_S3_ACCESS_KEY"),
SecretKey: os.Getenv("AWS_S3_SECRET_KEY"),
Region: os.Getenv("AWS_S3_REGION"),
Bucket: os.Getenv("AWS_S3_BUCKET"),
ObjectKey: "dir/config.json",
}), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
```
### Reading raw bytes
The bundled `rawbytes` Provider can be used to read arbitrary bytes from a source, like a database or an HTTP call.
```go
package main
import (
"fmt"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/rawbytes"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf.New(".")
func main() {
b := []byte(`{"type": "rawbytes", "parent1": {"child1": {"type": "rawbytes"}}}`)
k.Load(rawbytes.Provider(b), json.Parser())
fmt.Println("type is = ", k.String("parent1.child1.type"))
}
```
### Unmarshalling and marshalling
`Parser`s can be used to unmarshal and scan the values in a Koanf instance into a struct based on the field tags, and to marshal a Koanf instance back into serialized bytes, for example, back to JSON or YAML, to write back to files.
```go
package main
import (
"fmt"
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var (
k = koanf.New(".")
parser = json.Parser()
)
func main() {
// Load JSON config.
if err := k.Load(file.Provider("mock/mock.json"), parser); err != nil {
log.Fatalf("error loading config: %v", err)
}
// Structure to unmarshal nested conf to.
type childStruct struct {
Name string `koanf:"name"`
Type string `koanf:"type"`
Empty map[string]string `koanf:"empty"`
GrandChild struct {
Ids []int `koanf:"ids"`
On bool `koanf:"on"`
} `koanf:"grandchild1"`
}
var out childStruct
// Quick unmarshal.
k.Unmarshal("parent1.child1", &out)
fmt.Println(out)
// Unmarshal with advanced config.
out = childStruct{}
k.UnmarshalWithConf("parent1.child1", &out, koanf.UnmarshalConf{Tag: "koanf"})
fmt.Println(out)
// Marshal the instance back to JSON.
// The paser instance can be anything, eg: json.Paser(), yaml.Parser() etc.
b, _ := k.Marshal(parser)
fmt.Println(string(b))
}
```
### Unmarshalling with flat paths
Sometimes it is necessary to unmarshal an assortment of keys from various nested structures into a flat target structure. This is possible with the `UnmarshalConf.FlatPaths` flag.
```go
package main
import (
"fmt"
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/providers/file"
)
// Global koanf instance. Use . as the key path delimiter. This can be / or anything.
var k = koanf.New(".")
func main() {
// Load JSON config.
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
type rootFlat struct {
Type string `koanf:"type"`
Empty map[string]string `koanf:"empty"`
Parent1Name string `koanf:"parent1.name"`
Parent1ID int `koanf:"parent1.id"`
Parent1Child1Name string `koanf:"parent1.child1.name"`
Parent1Child1Type string `koanf:"parent1.child1.type"`
Parent1Child1Empty map[string]string `koanf:"parent1.child1.empty"`
Parent1Child1Grandchild1IDs []int `koanf:"parent1.child1.grandchild1.ids"`
Parent1Child1Grandchild1On bool `koanf:"parent1.child1.grandchild1.on"`
}
// Unmarshal the whole root with FlatPaths: True.
var o1 rootFlat
k.UnmarshalWithConf("", &o1, koanf.UnmarshalConf{Tag: "koanf", FlatPaths: true})
fmt.Println(o1)
// Unmarshal a child structure of "parent1".
type subFlat struct {
Name string `koanf:"name"`
ID int `koanf:"id"`
Child1Name string `koanf:"child1.name"`
Child1Type string `koanf:"child1.type"`
Child1Empty map[string]string `koanf:"child1.empty"`
Child1Grandchild1IDs []int `koanf:"child1.grandchild1.ids"`
Child1Grandchild1On bool `koanf:"child1.grandchild1.on"`
}
var o2 subFlat
k.UnmarshalWithConf("parent1", &o2, koanf.UnmarshalConf{Tag: "koanf", FlatPaths: true})
fmt.Println(o2)
}
```
### Marshalling and writing config
It is possible to marshal and serialize the conf map into TOML, YAML etc.
### Setting default values.
koanf does not provide any special functions to set default values but uses the Provider interface to enable it.
#### From a map
The bundled `confmap` provider takes a `map[string]interface{}` that can be loaded into a koanf instance.
```go
package main
import (
"fmt"
"log"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/parsers/json"
"github.com/knadh/koanf/parsers/yaml"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
func main() {
// Load default values using the confmap provider.
// We provide a flat map with the "." delimiter.
// A nested map can be loaded by setting the delimiter to an empty string "".
k.Load(confmap.Provider(map[string]interface{}{
"parent1.name": "Default Name",
"parent3.name": "New name here",
}, "."), nil)
// Load JSON config on top of the default values.
if err := k.Load(file.Provider("mock/mock.json"), json.Parser()); err != nil {
log.Fatalf("error loading config: %v", err)
}
// Load YAML config and merge into the previously loaded config (because we can).
k.Load(file.Provider("mock/mock.yaml"), yaml.Parser())
fmt.Println("parent's name is = ", k.String("parent1.name"))
fmt.Println("parent's ID is = ", k.Int("parent1.id"))
}
```
#### From a struct
The bundled `structs` provider can be used to read data from a struct to load into a koanf instance.
```go
package main
import (
"fmt"
"github.com/knadh/koanf"
"github.com/knadh/koanf/providers/structs"
)
// Global koanf instance. Use "." as the key path delimiter. This can be "/" or any character.
var k = koanf.New(".")
type parentStruct struct {
Name string `koanf:"name"`
ID int `koanf:"id"`
Child1 childStruct `koanf:"child1"`
}
type childStruct struct {
Name string `koanf:"name"`
Type string `koanf:"type"`
Empty map[string]string `koanf:"empty"`
Grandchild1 grandchildStruct `koanf:"grandchild1"`
}
type grandchildStruct struct {
Ids []int `koanf:"ids"`
On bool `koanf:"on"`
}
type sampleStruct struct {
Type string `koanf:"type"`
Empty map[string]string `koanf:"empty"`
Parent1 parentStruct `koanf:"parent1"`
}
func main() {
// Load default values using the structs provider.
// We provide a struct along with the struct tag `koanf` to the
// provider.
k.Load(structs.Provider(sampleStruct{
Type: "json",
Empty: make(map[string]string),
Parent1: parentStruct{
Name: "parent1",
ID: 1234,
Child1: childStruct{
Name: "child1",
Type: "json",
Empty: make(map[string]string),
Grandchild1: grandchildStruct{
Ids: []int{1, 2, 3},
On: true,
},
},
},
}, "koanf"), nil)
fmt.Printf("name is = `%s`\n", k.String("parent1.child1.name"))
}
```
### Order of merge and key case senstivity
- Config keys are case-sensitive in koanf. For example, `app.server.port` and `APP.SERVER.port` are not the same.
- koanf does not impose any ordering on loading config from various providers. Every successive `Load()` or `Load()` merges new config into existing config. That means it is possible to load environment variables first, then files on top of it, and then command line variables on top of it, or any such order.
### Custom Providers and Parsers
A Provider can provide a nested map[string]interface{} config that can be loaded into koanf with `koanf.Load()` or raw bytes that can be parsed with a Parser (loaded using `koanf.Load()`.
Writing Providers and Parsers are easy. See the bundled implementations in the `providers` and `parses` directory.
## API
### Bundled providers
| Package | Provider | Description |
| ------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| providers/file | `file.Provider(filepath string)` | Reads a file and returns the raw bytes to be parsed. |
| providers/basicflag | `basicflag.Provider(f *flag.FlagSet, delim string)` | Takes an stdlib `flag.FlagSet` |
| providers/posflag | `posflag.Provider(f *pflag.FlagSet, delim string)` | Takes an `spft3/pflag.FlagSet` (advanced POSIX compatible flags with multiple types) and provides a nested config map based on delim. |
| providers/env | `env.Provider(prefix, delim string, f func(s string) string)` | Takes an optional prefix to filter env variables by, an optional function that takes and returns a string to transform env variables, and returns a nested config map based on delim. |
| providers/confmap | `confmap.Provider(mp map[string]interface{}, delim string)` | Takes a premade `map[string]interface{}` conf map. If delim is provided, the keys are assumed to be flattened, thus unflattened using delim. |
| providers/structs | `structs.Provider(s interface{}, tag string)` | Takes a struct and struct tag. |
| providers/s3 | `s3.Provider(s3.S3Config{})` | Takes a s3 config struct. |
| providers/rawbytes | `rawbytes.Provider(b []byte)` | Takes a raw `[]byte` slice to be parsed with a koanf.Parser |
### Bundled parsers
| Package | Parser | Description |
| ------------ | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| parsers/json | `json.Parser()` | Parses JSON bytes into a nested map |
| parsers/yaml | `yaml.Parser()` | Parses YAML bytes into a nested map |
| parsers/toml | `toml.Parser()` | Parses TOML bytes into a nested map |
| parsers/dotenv | `dotenv.Parser()` | Parses DotEnv bytes into a flat map |
| parsers/hcl | `hcl.Parser(flattenSlices bool)` | Parses Hashicorp HCL bytes into a nested map. `flattenSlices` is recommended to be set to true. [Read more](https://github.com/hashicorp/hcl/issues/162). |
### Instance functions
| Method | Description |
| ---------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `Load(p Provider, pa Parser) error` | Loads config from a Provider. If a koanf.Parser is provided, the config is assumed to be raw bytes that's then parsed with the Parser. |
| `Keys() []string` | Returns the list of flattened key paths that can be used to access config values |
| `KeyMap() map[string][]string` | Returns a map of all possible key path combinations possible in the loaded nested conf map |
| `All() map[string]interface{}` | Returns a flat map of flattened key paths and their corresponding config values |
| `Raw() map[string]interface{}` | Returns a copy of the raw nested conf map |
| `Print()` | Prints a human readable copy of the flattened key paths and their values for debugging |
| `Sprint()` | Returns a human readable copy of the flattened key paths and their values for debugging |
| `Cut(path string) *Koanf` | Cuts the loaded nested conf map at the given path and returns a new Koanf instance with the children |
| `Copy() *Koanf` | Returns a copy of the Koanf instance |
| `Merge(*Koanf)` | Merges the config map of a Koanf instance into the current instance |
| `MergeAt(in *Koanf, path string)` | Merges the config map of a Koanf instance into the current instance, at the given key path. |
| `Unmarshal(path string, o interface{}) error` | Scans the given nested key path into a given struct (like json.Unmarshal) where fields are denoted by the `koanf` tag |
| `UnmarshalWithConf(path string, o interface{}, c UnmarshalConf) error` | Like Unmarshal but with customizable options |
### Getter functions
| | |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Get(path string) interface{}` | Returns the value for the given key path, and if it doesnt exist, returns nil |
| `Exists(path string) bool` | Returns true if the given key path exists in the conf map |
| `Int64(path string) int64` | |
| `Int64s(path string) []int64` | |
| `Int64Map(path string) map[string]int64` | |
| `Int(path string) int` | |
| `Ints(path string) []int` | |
| `IntMap(path string) map[string]int` | |
| `Float64(path string) float64` | |
| `Float64s(path string) []float64` | |
| `Float64Map(path string) map[string]float64` | |
| `Duration(path string) time.Duration` | Returns the time.Duration value of the given key path if its numeric (attempts a parse+convert if string) or a string representation like "3s". |
| `Time(path, layout string) time.Time` | Parses the string value of the the given key path with the given layout format and returns time.Time. If the key path is numeric, treats it as a UNIX timestamp and returns its time.Time. |
| `String(path string) string` | |
| `Strings(path string) []string` | |
| `StringMap(path string) map[string]string` | |
| `StringsMap(path string) map[string][]string` | |
| `Byte(path string) []byte` | |
| `Bool(path string) bool` | |
| `Bools(path string) []bool` | |
| `BoolMap(path string) map[string]bool` | |
| `MapKeys(path string) []string` | Returns the list of keys in any map |
| `Slices(path string) []Koanf` | Returns `[]map[string]interface{}`, a slice of confmaps loaded into a slice of new Koanf instances. |
### Alternative to viper
koanf is a lightweight alternative to the popular [spf13/viper](https://github.com/spf13/viper). It does not aim to do everything viper does (such as mutating config maps and writing them back to files), but provides simpler primitives for reading and accessing configuration. It was written as a result of multiple stumbling blocks encountered with some of viper's fundamental flaws.
- viper breaks JSON, YAML, TOML, HCL language specs by [forcibly lowercasing keys](https://github.com/spf13/viper/pull/635).
- Significantly bloats [build sizes](https://github.com/knadh/koanf/wiki/Comparison-with-spf13-viper).
- Tightly couples config parsing with file extensions.
- Has poor semantics and abstractions. Commandline, env, file etc. and various parses are hardcoded in the core. There are no primitives that can be extended.
- Pulls a large number of [third party dependencies](https://github.com/spf13/viper/issues/707) into the core package. For instance, even if you do not use YAML or flags, the dependencies are still pulled as a result of the coupling.
- Imposes arbitrary ordering conventions (eg: flag -> env -> config etc.)
- `Get()` returns references to slices and maps. Mutations made outside change the underlying values inside the conf map.
- Does non-idiomatic things such as [throwing away O(1) on flat maps](https://github.com/spf13/viper/blob/3b4aca75714a37276c4b1883630bd98c02498b73/viper.go#L1524).
- There are a large number of [open issues](https://github.com/spf13/viper/issues).

588
vendor/github.com/knadh/koanf/getters.go generated vendored Normal file
View File

@ -0,0 +1,588 @@
package koanf
import (
"fmt"
"time"
)
// Int64 returns the int64 value of a given key path or 0 if the path
// does not exist or if the value is not a valid int64.
func (ko *Koanf) Int64(path string) int64 {
if v := ko.Get(path); v != nil {
i, _ := toInt64(v)
return i
}
return 0
}
// MustInt64 returns the int64 value of a given key path or panics
// if the value is not set or set to default value of 0.
func (ko *Koanf) MustInt64(path string) int64 {
val := ko.Int64(path)
if val == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Int64s returns the []int64 slice value of a given key path or an
// empty []int64 slice if the path does not exist or if the value
// is not a valid int slice.
func (ko *Koanf) Int64s(path string) []int64 {
o := ko.Get(path)
if o == nil {
return []int64{}
}
var out []int64
switch v := o.(type) {
case []interface{}:
out = make([]int64, 0, len(v))
for _, vi := range v {
i, err := toInt64(vi)
// On error, return as it's not a valid
// int slice.
if err != nil {
return []int64{}
}
out = append(out, i)
}
return out
}
return []int64{}
}
// MustInt64s returns the []int64 slice value of a given key path or panics
// if the value is not set or its default value.
func (ko *Koanf) MustInt64s(path string) []int64 {
val := ko.Int64s(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Int64Map returns the map[string]int64 value of a given key path
// or an empty map[string]int64 if the path does not exist or if the
// value is not a valid int64 map.
func (ko *Koanf) Int64Map(path string) map[string]int64 {
var (
out = map[string]int64{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make(map[string]int64, len(mp))
for k, v := range mp {
switch i := v.(type) {
case int64:
out[k] = i
default:
// Attempt a conversion.
iv, err := toInt64(i)
if err != nil {
return map[string]int64{}
}
out[k] = iv
}
}
return out
}
// MustInt64Map returns the map[string]int64 value of a given key path
// or panics if its not set or set to default value.
func (ko *Koanf) MustInt64Map(path string) map[string]int64 {
val := ko.Int64Map(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Int returns the int value of a given key path or 0 if the path
// does not exist or if the value is not a valid int.
func (ko *Koanf) Int(path string) int {
return int(ko.Int64(path))
}
// MustInt returns the int value of a given key path or panics
// or panics if its not set or set to default value of 0.
func (ko *Koanf) MustInt(path string) int {
val := ko.Int(path)
if val == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Ints returns the []int slice value of a given key path or an
// empty []int slice if the path does not exist or if the value
// is not a valid int slice.
func (ko *Koanf) Ints(path string) []int {
ints := ko.Int64s(path)
if len(ints) == 0 {
return []int{}
}
out := make([]int, len(ints))
for i, v := range ints {
out[i] = int(v)
}
return out
}
// MustInts returns the []int slice value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustInts(path string) []int {
val := ko.Ints(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// IntMap returns the map[string]int value of a given key path
// or an empty map[string]int if the path does not exist or if the
// value is not a valid int map.
func (ko *Koanf) IntMap(path string) map[string]int {
var (
mp = ko.Int64Map(path)
out = make(map[string]int, len(mp))
)
for k, v := range mp {
out[k] = int(v)
}
return out
}
// MustIntMap returns the map[string]int value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustIntMap(path string) map[string]int {
val := ko.IntMap(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Float64 returns the float64 value of a given key path or 0 if the path
// does not exist or if the value is not a valid float64.
func (ko *Koanf) Float64(path string) float64 {
if v := ko.Get(path); v != nil {
f, _ := toFloat64(v)
return f
}
return 0
}
// MustFloat64 returns the float64 value of a given key path or panics
// or panics if its not set or set to default value 0.
func (ko *Koanf) MustFloat64(path string) float64 {
val := ko.Float64(path)
if val == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Float64s returns the []float64 slice value of a given key path or an
// empty []float64 slice if the path does not exist or if the value
// is not a valid float64 slice.
func (ko *Koanf) Float64s(path string) []float64 {
o := ko.Get(path)
if o == nil {
return []float64{}
}
var out []float64
switch v := o.(type) {
case []interface{}:
out = make([]float64, 0, len(v))
for _, vi := range v {
i, err := toFloat64(vi)
// On error, return as it's not a valid
// int slice.
if err != nil {
return []float64{}
}
out = append(out, float64(i))
}
return out
}
return []float64{}
}
// MustFloat64s returns the []Float64 slice value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustFloat64s(path string) []float64 {
val := ko.Float64s(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Float64Map returns the map[string]float64 value of a given key path
// or an empty map[string]float64 if the path does not exist or if the
// value is not a valid float64 map.
func (ko *Koanf) Float64Map(path string) map[string]float64 {
var (
out = map[string]float64{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make(map[string]float64, len(mp))
for k, v := range mp {
switch i := v.(type) {
case float64:
out[k] = i
default:
// Attempt a conversion.
iv, err := toFloat64(i)
if err != nil {
return map[string]float64{}
}
out[k] = iv
}
}
return out
}
// MustFloat64Map returns the map[string]float64 value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustFloat64Map(path string) map[string]float64 {
val := ko.Float64Map(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Duration returns the time.Duration value of a given key path assuming
// that the key contains a valid numeric value.
func (ko *Koanf) Duration(path string) time.Duration {
// Look for a parsable string representation first.
if v := ko.Int64(path); v > 0 {
return time.Duration(v)
}
v, _ := time.ParseDuration(ko.String(path))
return v
}
// MustDuration returns the time.Duration value of a given key path or panics
// if its not set or set to default value 0.
func (ko *Koanf) MustDuration(path string) time.Duration {
val := ko.Duration(path)
if val == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Time attempts to parse the value of a given key path and return time.Time
// representation. If the value is numeric, it is treated as a UNIX timestamp
// and if it's string, a parse is attempted with the given layout.
func (ko *Koanf) Time(path, layout string) time.Time {
// Unix timestamp?
v := ko.Int64(path)
if v != 0 {
return time.Unix(v, 0)
}
// String representation.
s := ko.String(path)
if s != "" {
t, _ := time.Parse(layout, s)
return t
}
return time.Time{}
}
// MustTime attempts to parse the value of a given key path and return time.Time
// representation. If the value is numeric, it is treated as a UNIX timestamp
// and if it's string, a parse is attempted with the given layout. It panics if
// the parsed time is zero.
func (ko *Koanf) MustTime(path, layout string) time.Time {
val := ko.Time(path, layout)
if val.IsZero() {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// String returns the string value of a given key path or "" if the path
// does not exist or if the value is not a valid string.
func (ko *Koanf) String(path string) string {
if v := ko.Get(path); v != nil {
if i, ok := v.(string); ok {
return i
}
return fmt.Sprintf("%v", v)
}
return ""
}
// MustString returns the string value of a given key path
// or panics if its not set or set to default value "".
func (ko *Koanf) MustString(path string) string {
val := ko.String(path)
if val == "" {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Strings returns the []string slice value of a given key path or an
// empty []string slice if the path does not exist or if the value
// is not a valid string slice.
func (ko *Koanf) Strings(path string) []string {
o := ko.Get(path)
if o == nil {
return []string{}
}
var out []string
switch v := o.(type) {
case []interface{}:
out = make([]string, 0, len(v))
for _, u := range v {
if s, ok := u.(string); ok {
out = append(out, s)
} else {
out = append(out, fmt.Sprintf("%v", u))
}
}
return out
case []string:
out := make([]string, len(v))
copy(out[:], v[:])
return out
}
return []string{}
}
// MustStrings returns the []string slice value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustStrings(path string) []string {
val := ko.Strings(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// StringMap returns the map[string]string value of a given key path
// or an empty map[string]string if the path does not exist or if the
// value is not a valid string map.
func (ko *Koanf) StringMap(path string) map[string]string {
var (
out = map[string]string{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make(map[string]string, len(mp))
for k, v := range mp {
switch s := v.(type) {
case string:
out[k] = s
default:
// There's a non string type. Return.
return map[string]string{}
}
}
return out
}
// MustStringMap returns the map[string]string value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustStringMap(path string) map[string]string {
val := ko.StringMap(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// StringsMap returns the map[string][]string value of a given key path
// or an empty map[string][]string if the path does not exist or if the
// value is not a valid strings map.
func (ko *Koanf) StringsMap(path string) map[string][]string {
var (
out = map[string][]string{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make(map[string][]string, len(mp))
for k, v := range mp {
switch s := v.(type) {
case []interface{}:
for _, v := range s {
switch sv := v.(type) {
case string:
out[k] = append(out[k], sv)
default:
return map[string][]string{}
}
}
default:
// There's a non []interface type. Return.
return map[string][]string{}
}
}
return out
}
// MustStringsMap returns the map[string][]string value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustStringsMap(path string) map[string][]string {
val := ko.StringsMap(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Bytes returns the []byte value of a given key path or an empty
// []byte slice if the path does not exist or if the value is not a valid string.
func (ko *Koanf) Bytes(path string) []byte {
return []byte(ko.String(path))
}
// MustBytes returns the []byte value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustBytes(path string) []byte {
val := ko.Bytes(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// Bool returns the bool value of a given key path or false if the path
// does not exist or if the value is not a valid bool representation.
// Accepted string representations of bool are the ones supported by strconv.ParseBool.
func (ko *Koanf) Bool(path string) bool {
if v := ko.Get(path); v != nil {
b, _ := toBool(v)
return b
}
return false
}
// Bools returns the []bool slice value of a given key path or an
// empty []bool slice if the path does not exist or if the value
// is not a valid bool slice.
func (ko *Koanf) Bools(path string) []bool {
o := ko.Get(path)
if o == nil {
return []bool{}
}
var out []bool
switch v := o.(type) {
case []interface{}:
out = make([]bool, 0, len(v))
for _, u := range v {
b, err := toBool(u)
if err != nil {
return nil
}
out = append(out, b)
}
return out
case []bool:
return out
}
return nil
}
// MustBools returns the []bool value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustBools(path string) []bool {
val := ko.Bools(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}
// BoolMap returns the map[string]bool value of a given key path
// or an empty map[string]bool if the path does not exist or if the
// value is not a valid bool map.
func (ko *Koanf) BoolMap(path string) map[string]bool {
var (
out = map[string]bool{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make(map[string]bool, len(mp))
for k, v := range mp {
switch i := v.(type) {
case bool:
out[k] = i
default:
// Attempt a conversion.
b, err := toBool(i)
if err != nil {
return map[string]bool{}
}
out[k] = b
}
}
return out
}
// MustBoolMap returns the map[string]bool value of a given key path or panics
// if the value is not set or set to default value.
func (ko *Koanf) MustBoolMap(path string) map[string]bool {
val := ko.BoolMap(path)
if len(val) == 0 {
panic(fmt.Sprintf("invalid value: %s=%v", path, val))
}
return val
}

17
vendor/github.com/knadh/koanf/go.mod generated vendored Normal file
View File

@ -0,0 +1,17 @@
module github.com/knadh/koanf
go 1.12
require (
github.com/fatih/structs v1.1.0
github.com/fsnotify/fsnotify v1.4.9
github.com/hashicorp/hcl v1.0.0
github.com/joho/godotenv v1.3.0 // indirect
github.com/mitchellh/mapstructure v1.2.2
github.com/pelletier/go-toml v1.7.0
github.com/rhnvrm/simples3 v0.5.0
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.3.0
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d // indirect
gopkg.in/yaml.v2 v2.2.8
)

34
vendor/github.com/knadh/koanf/go.sum generated vendored Normal file
View File

@ -0,0 +1,34 @@
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/mitchellh/mapstructure v1.2.2 h1:dxe5oCinTXiTIcfgmZecdCzPmAJKd46KsCWc35r0TV4=
github.com/mitchellh/mapstructure v1.2.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rhnvrm/simples3 v0.5.0 h1:X+WX0hqoKScdoJAw/G3GArfZ6Ygsn8q+6MdocTMKXOw=
github.com/rhnvrm/simples3 v0.5.0/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

25
vendor/github.com/knadh/koanf/interfaces.go generated vendored Normal file
View File

@ -0,0 +1,25 @@
package koanf
// Provider represents a configuration provider. Providers can
// read configuration from a source (file, HTTP etc.)
type Provider interface {
// Read returns the entire configuration as raw []bytes to be parsed.
// with a Parser.
ReadBytes() ([]byte, error)
// Read returns the prased configuration as a nested map[string]interface{}.
// It is important to note that the string keys should not be flat delimited
// keys like `parent.child.key`, but nested like `{parent: {child: {key: 1}}}`.
Read() (map[string]interface{}, error)
// Watch watches the source for changes, for instance, changes to a file,
// and invokes a callback with an `event` interface, which a provider
// is free to substitute with its own type, including nil.
Watch(func(event interface{}, err error)) error
}
// Parser represents a configuration format parser.
type Parser interface {
Unmarshal([]byte) (map[string]interface{}, error)
Marshal(map[string]interface{}) ([]byte, error)
}

429
vendor/github.com/knadh/koanf/koanf.go generated vendored Normal file
View File

@ -0,0 +1,429 @@
package koanf
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strconv"
"strings"
"github.com/knadh/koanf/maps"
"github.com/knadh/koanf/providers/confmap"
"github.com/mitchellh/mapstructure"
)
// Koanf is the configuration apparatus.
type Koanf struct {
confMap map[string]interface{}
confMapFlat map[string]interface{}
keyMap KeyMap
delim string
}
// KeyMap represents a map of flattened delimited keys and the non-delimited
// parts as their slices. For nested keys, the map holds all levels of path combinations.
// For example, the nested structure `parent -> child -> key` will produce the map:
// parent.child.key => [parent, child, key]
// parent.child => [parent, child]
// parent => [parent]
type KeyMap map[string][]string
// UnmarshalConf represents configuration options used by
// Unmarshal() to unmarshal conf maps into arbitrary structs.
type UnmarshalConf struct {
// Tag is the struct field tag to unmarshal.
// `koanf` is used if left empty.
Tag string
// If this is set to true, instead of unmarshalling nested structures
// based on the key path, keys are taken literally to unmarshal into
// a flat struct. For example:
// ```
// type MyStuff struct {
// Child1Name string `koanf:"parent1.child1.name"`
// Child2Name string `koanf:"parent2.child2.name"`
// Type string `koanf:"json"`
// }
// ```
FlatPaths bool
DecoderConfig *mapstructure.DecoderConfig
}
// New returns a new instance of Koanf. delim is the delimiter to use
// when specifying config key paths, for instance a . for `parent.child.key`
// or a / for `parent/child/key`.
func New(delim string) *Koanf {
return &Koanf{
delim: delim,
confMap: make(map[string]interface{}),
confMapFlat: make(map[string]interface{}),
keyMap: make(KeyMap),
}
}
// Load takes a Provider that either provides a parsed config map[string]interface{}
// in which case pa (Parser) can be nil, or raw bytes to be parsed, where a Parser
// can be provided to parse.
func (ko *Koanf) Load(p Provider, pa Parser) error {
var (
mp map[string]interface{}
err error
)
// No Parser is given. Call the Provider's Read() method to get
// the config map.
if pa == nil {
mp, err = p.Read()
if err != nil {
return err
}
} else {
// There's a Parser. Get raw bytes from the Provider to parse.
b, err := p.ReadBytes()
if err != nil {
return err
}
mp, err = pa.Unmarshal(b)
if err != nil {
return err
}
}
ko.merge(mp)
return nil
}
// Keys returns the slice of all flattened keys in the loaded configuration
// sorted alphabetically.
func (ko *Koanf) Keys() []string {
out := make([]string, 0, len(ko.confMapFlat))
for k := range ko.confMapFlat {
out = append(out, k)
}
sort.Strings(out)
return out
}
// KeyMap returns a map of flattened keys and the individual parts of the
// key as slices. eg: "parent.child.key" => ["parent", "child", "key"]
func (ko *Koanf) KeyMap() KeyMap {
out := make(KeyMap, len(ko.keyMap))
for key, parts := range ko.keyMap {
out[key] = make([]string, len(parts))
copy(out[key][:], parts[:])
}
return out
}
// All returns a map of all flattened key paths and their values.
// Note that it uses maps.Copy to create a copy that uses
// json.Marshal which changes the numeric types to float64.
func (ko *Koanf) All() map[string]interface{} {
return maps.Copy(ko.confMapFlat)
}
// Raw returns a copy of the full raw conf map.
// Note that it uses maps.Copy to create a copy that uses
// json.Marshal which changes the numeric types to float64.
func (ko *Koanf) Raw() map[string]interface{} {
return maps.Copy(ko.confMap)
}
// Sprint returns a key -> value string representation
// of the config map with keys sorted alphabetically.
func (ko *Koanf) Sprint() string {
b := bytes.Buffer{}
for _, k := range ko.Keys() {
b.Write([]byte(fmt.Sprintf("%s -> %v\n", k, ko.confMapFlat[k])))
}
return b.String()
}
// Print prints a key -> value string representation
// of the config map with keys sorted alphabetically.
func (ko *Koanf) Print() {
fmt.Print(ko.Sprint())
}
// Cut cuts the config map at a given key path into a sub map and
// returns a new Koanf instance with the cut config map loaded.
// For instance, if the loaded config has a path that looks like
// parent.child.sub.a.b, `Cut("parent.child")` returns a new Koanf
// instance with the config map `sub.a.b` where everything above
// `parent.child` are cut out.
func (ko *Koanf) Cut(path string) *Koanf {
out := make(map[string]interface{})
// Cut only makes sense if the requested key path is a map.
if v, ok := ko.Get(path).(map[string]interface{}); ok {
out = v
}
n := New(ko.delim)
n.merge(out)
return n
}
// Copy returns a copy of the Koanf instance.
func (ko *Koanf) Copy() *Koanf {
return ko.Cut("")
}
// Merge merges the config map of a given Koanf instance into
// the current instance.
func (ko *Koanf) Merge(in *Koanf) {
ko.merge(in.Raw())
}
// MergeAt merges the config map of a given Koanf instance into
// the current instance as a sub map, at the given key path.
// If all or part of the key path is missing, it will be created.
// If the key path is `""`, this is equivalent to Merge.
func (ko *Koanf) MergeAt(in *Koanf, path string) {
// No path. Merge the two config maps.
if path == "" {
ko.Merge(in)
return
}
// Unflatten the config map with the given key path.
n := maps.Unflatten(map[string]interface{}{
path: in.Raw(),
}, ko.delim)
ko.merge(n)
}
// Marshal takes a Parser implementation and marshals the config map into bytes,
// for example, to TOML or JSON bytes.
func (ko *Koanf) Marshal(p Parser) ([]byte, error) {
return p.Marshal(ko.Raw())
}
// Unmarshal unmarshals a given key path into the given struct using
// the mapstructure lib. If no path is specified, the whole map is unmarshalled.
// `koanf` is the struct field tag used to match field names. To customize,
// use UnmarshalWithConf(). It uses the mitchellh/mapstructure package.
func (ko *Koanf) Unmarshal(path string, o interface{}) error {
return ko.UnmarshalWithConf(path, o, UnmarshalConf{})
}
// UnmarshalWithConf is like Unmarshal but takes configuration params in UnmarshalConf.
// See mitchellh/mapstructure's DecoderConfig for advanced customization
// of the unmarshal behaviour.
func (ko *Koanf) UnmarshalWithConf(path string, o interface{}, c UnmarshalConf) error {
if c.DecoderConfig == nil {
c.DecoderConfig = &mapstructure.DecoderConfig{
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc()),
Metadata: nil,
Result: o,
WeaklyTypedInput: true,
}
}
if c.Tag == "" {
c.DecoderConfig.TagName = "koanf"
} else {
c.DecoderConfig.TagName = c.Tag
}
d, err := mapstructure.NewDecoder(c.DecoderConfig)
if err != nil {
return err
}
// Unmarshal using flat key paths.
mp := ko.Get(path)
if c.FlatPaths {
if f, ok := mp.(map[string]interface{}); ok {
fmp, _ := maps.Flatten(f, nil, ko.delim)
mp = fmp
}
}
return d.Decode(mp)
}
// Get returns the raw, uncast interface{} value of a given key path
// in the config map. If the key path does not exist, nil is returned.
func (ko *Koanf) Get(path string) interface{} {
// No path. Return the whole conf map.
if path == "" {
return ko.Raw()
}
// Does the path exist?
p, ok := ko.keyMap[path]
if !ok {
return nil
}
res := maps.Search(ko.confMap, p)
// Non-reference types are okay to return directly.
// Other types are "copied" with maps.Copy or json.Marshal
// that change the numeric types to float64.
switch v := res.(type) {
case int, int8, int16, int32, int64, float32, float64, string, bool:
return v
case map[string]interface{}:
return maps.Copy(v)
}
// Inefficient, but marshal and unmarshal to create a copy
// of reference types to not expose internal references to slices and maps.
var out interface{}
b, _ := json.Marshal(res)
json.Unmarshal(b, &out)
return out
}
// Slices returns a list of Koanf instances constructed out of a
// []map[string]interface{} interface at the given path.
func (ko *Koanf) Slices(path string) []*Koanf {
out := []*Koanf{}
if path == "" {
return out
}
// Does the path exist?
sl, ok := ko.Get(path).([]interface{})
if !ok {
return out
}
for _, s := range sl {
v, ok := s.(map[string]interface{})
if !ok {
continue
}
k := New(ko.delim)
k.Load(confmap.Provider(v, ""), nil)
out = append(out, k)
}
return out
}
// Exists returns true if the given key path exists in the conf map.
func (ko *Koanf) Exists(path string) bool {
_, ok := ko.keyMap[path]
return ok
}
// MapKeys returns a sorted string list of keys in a map addressed by the
// given path. If the path is not a map, an empty string slice is
// returned.
func (ko *Koanf) MapKeys(path string) []string {
var (
out = []string{}
o = ko.Get(path)
)
if o == nil {
return out
}
mp, ok := o.(map[string]interface{})
if !ok {
return out
}
out = make([]string, 0, len(mp))
for k := range mp {
out = append(out, k)
}
sort.Strings(out)
return out
}
func (ko *Koanf) merge(c map[string]interface{}) {
maps.IntfaceKeysToStrings(c)
maps.Merge(c, ko.confMap)
// Maintain a flattened version as well.
ko.confMapFlat, ko.keyMap = maps.Flatten(ko.confMap, nil, ko.delim)
ko.keyMap = populateKeyParts(ko.keyMap, ko.delim)
}
// toInt64 takes an interface value and if it is an integer type,
// converts and returns int64. If it's any other type,
// forces it to a string and attempts to an strconv.Atoi
// to get an integer out.
func toInt64(v interface{}) (int64, error) {
switch i := v.(type) {
case int:
return int64(i), nil
case int8:
return int64(i), nil
case int16:
return int64(i), nil
case int32:
return int64(i), nil
case int64:
return i, nil
}
// Force it to a string and try to convert.
f, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64)
if err != nil {
return 0, err
}
return int64(f), nil
}
// toInt64 takes a `v interface{}` value and if it is a float type,
// converts and returns a `float64`. If it's any other type, forces it to a
// string and attempts to get a float out using `strconv.ParseFloat`.
func toFloat64(v interface{}) (float64, error) {
switch i := v.(type) {
case float32:
return float64(i), nil
case float64:
return i, nil
}
// Force it to a string and try to convert.
f, err := strconv.ParseFloat(fmt.Sprintf("%v", v), 64)
if err != nil {
return f, err
}
return f, nil
}
// toBool takes an interface value and if it is a bool type,
// returns it. If it's any other type, forces it to a string and attempts
// to parse it as a bool using strconv.ParseBool.
func toBool(v interface{}) (bool, error) {
if b, ok := v.(bool); ok {
return b, nil
}
// Force it to a string and try to convert.
b, err := strconv.ParseBool(fmt.Sprintf("%v", v))
if err != nil {
return b, err
}
return b, nil
}
// populateKeyParts iterates a key map and generates all possible
// traversal paths. For instance, `parent.child.key` generates
// `parent`, and `parent.child`.
func populateKeyParts(m KeyMap, delim string) KeyMap {
out := make(KeyMap)
for _, parts := range m {
for i := range parts {
nk := strings.Join(parts[0:i+1], delim)
if _, ok := out[nk]; ok {
continue
}
out[nk] = make([]string, i+1)
copy(out[nk][:], parts[0:i+1])
}
}
return out
}

224
vendor/github.com/knadh/koanf/maps/maps.go generated vendored Normal file
View File

@ -0,0 +1,224 @@
// Package maps provides reusable functions for manipulating nested
// map[string]interface{} maps are common unmarshal products from
// various serializers such as json, yaml etc.
package maps
import (
"encoding/json"
"fmt"
"strings"
)
// Flatten takes a map[string]interface{} and traverses it and flattens
// nested children into keys delimited by delim.
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
//
// eg: `{ "parent": { "child": 123 }}` becomes `{ "parent.child": 123 }`
// In addition, it keeps track of and returns a map of the delimited keypaths with
// a slice of key parts, for eg: { "parent.child": ["parent", "child"] }. This
// parts list is used to remember the key path's original structure to
// unflatten later.
func Flatten(m map[string]interface{}, keys []string, delim string) (map[string]interface{}, map[string][]string) {
var (
out = make(map[string]interface{})
keyMap = make(map[string][]string)
)
for key, val := range m {
// Copy the incoming key paths into a fresh list
// and append the current key in the iteration.
kp := make([]string, 0, len(keys)+1)
kp = append(kp, keys...)
kp = append(kp, key)
switch cur := val.(type) {
case map[string]interface{}:
// Empty map.
if len(cur) == 0 {
newKey := strings.Join(kp, delim)
out[newKey] = val
keyMap[newKey] = kp
continue
}
// It's a nested map. Flatten it recursively.
next, parts := Flatten(cur, kp, delim)
// Copy the resultant key parts and the value maps.
for k, p := range parts {
keyMap[k] = p
}
for k, v := range next {
out[k] = v
}
default:
newKey := strings.Join(kp, delim)
out[newKey] = val
keyMap[newKey] = kp
}
}
return out, keyMap
}
// Unflatten takes a flattened key:value map (non-nested with delimited keys)
// and returns a nested map where the keys are split into hierarchies by the given
// delimiter. For instance, `parent.child.key: 1` to `{parent: {child: {key: 1}}}`
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
func Unflatten(m map[string]interface{}, delim string) map[string]interface{} {
out := make(map[string]interface{})
// Iterate through the flat conf map.
for k, v := range m {
var (
keys = strings.Split(k, delim)
next = out
)
// Iterate through key parts, for eg:, parent.child.key
// will be ["parent", "child", "key"]
for _, k := range keys[:len(keys)-1] {
sub, ok := next[k]
if !ok {
// If the key does not exist in the map, create it.
sub = make(map[string]interface{})
next[k] = sub
}
if n, ok := sub.(map[string]interface{}); ok {
next = n
}
}
// Assign the value.
next[keys[len(keys)-1]] = v
}
return out
}
// Merge recursively merges map a into b (left to right), mutating
// and expanding map b. Note that there's no copying involved, so
// map b will retain references to map a.
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
func Merge(a, b map[string]interface{}) {
for key, val := range a {
// Does the key exist in the target map?
// If no, add it and move on.
bVal, ok := b[key]
if !ok {
b[key] = val
continue
}
// If the incoming val is not a map, do a direct merge.
if _, ok := val.(map[string]interface{}); !ok {
b[key] = val
continue
}
// The source key and target keys are both maps. Merge them.
switch v := bVal.(type) {
case map[string]interface{}:
Merge(val.(map[string]interface{}), v)
default:
b[key] = val
}
}
}
// Search recursively searches a map for a given path. The path is
// the key map slice, for eg:, parent.child.key -> [parent child key].
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
func Search(mp map[string]interface{}, path []string) interface{} {
next, ok := mp[path[0]]
if ok {
if len(path) == 1 {
return next
}
switch next.(type) {
case map[string]interface{}:
return Search(next.(map[string]interface{}), path[1:])
default:
return nil
} //
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
}
return nil
}
// Copy returns a copy of a conf map by doing a JSON marshal+unmarshal
// pass. Inefficient, but creates a true deep copy. There is a side
// effect, that is, all numeric types change to float64.
//
// It's important to note that all nested maps should be
// map[string]interface{} and not map[interface{}]interface{}.
// Use IntfaceKeysToStrings() to convert if necessary.
func Copy(mp map[string]interface{}) map[string]interface{} {
var out map[string]interface{}
b, _ := json.Marshal(mp)
json.Unmarshal(b, &out)
return out
}
// IntfaceKeysToStrings recursively converts map[interface{}]interface{} to
// map[string]interface{}. Some parses such as YAML unmarshal return this.
func IntfaceKeysToStrings(mp map[string]interface{}) {
for key, val := range mp {
switch cur := val.(type) {
case map[interface{}]interface{}:
x := make(map[string]interface{})
for k, v := range cur {
x[fmt.Sprintf("%v", k)] = v
}
mp[key] = x
IntfaceKeysToStrings(x)
case []interface{}:
for i, v := range cur {
switch sub := v.(type) {
case map[interface{}]interface{}:
x := make(map[string]interface{})
for k, v := range sub {
x[fmt.Sprintf("%v", k)] = v
}
cur[i] = x
IntfaceKeysToStrings(x)
case map[string]interface{}:
IntfaceKeysToStrings(sub)
}
}
case map[string]interface{}:
IntfaceKeysToStrings(cur)
}
}
}
// StringSliceToLookupMap takes a slice of strings and returns a lookup map
// with the slice values as keys with true values.
func StringSliceToLookupMap(s []string) map[string]bool {
mp := make(map[string]bool, len(s))
for _, v := range s {
mp[v] = true
}
return mp
}
// Int64SliceToLookupMap takes a slice of int64s and returns a lookup map
// with the slice values as keys with true values.
func Int64SliceToLookupMap(s []int64) map[int64]bool {
mp := make(map[int64]bool, len(s))
for _, v := range s {
mp[v] = true
}
return mp
}

34
vendor/github.com/knadh/koanf/parsers/toml/toml.go generated vendored Normal file
View File

@ -0,0 +1,34 @@
// Package toml implements a koanf.Parser that parses TOML bytes as conf maps.
package toml
import (
"bytes"
"github.com/pelletier/go-toml"
)
// TOML implements a TOML parser.
type TOML struct{}
// Parser returns a TOML Parser.
func Parser() *TOML {
return &TOML{}
}
// Unmarshal parses the given TOML bytes.
func (p *TOML) Unmarshal(b []byte) (map[string]interface{}, error) {
r, err := toml.LoadReader(bytes.NewBuffer(b))
if err != nil {
return nil, err
}
return r.ToMap(), err
}
// Marshal marshals the given config map to TOML bytes.
func (p *TOML) Marshal(o map[string]interface{}) ([]byte, error) {
out, err := toml.TreeFromMap(o)
if err != nil {
return nil, err
}
return out.Marshal()
}

View File

@ -0,0 +1,42 @@
// Package confmap implements a koanf.Provider that takes nested
// and flat map[string]interface{} config maps and provides them
// to koanf.
package confmap
import (
"errors"
"github.com/knadh/koanf/maps"
)
// Confmap implements a raw map[string]interface{} provider.
type Confmap struct {
mp map[string]interface{}
}
// Provider returns a confmap Provider that takes a flat or nested
// map[string]interface{}. If a delim is provided, it indicates that the
// keys are flat and the map needs to be unflatted by delim.
func Provider(mp map[string]interface{}, delim string) *Confmap {
cp := maps.Copy(mp)
maps.IntfaceKeysToStrings(cp)
if delim != "" {
cp = maps.Unflatten(cp, delim)
}
return &Confmap{mp: cp}
}
// ReadBytes is not supported by the env provider.
func (e *Confmap) ReadBytes() ([]byte, error) {
return nil, errors.New("confmap provider does not support this method")
}
// Read returns the loaded map[string]interface{}.
func (e *Confmap) Read() (map[string]interface{}, error) {
return e.mp, nil
}
// Watch is not supported.
func (e *Confmap) Watch(cb func(event interface{}, err error)) error {
return errors.New("confmap provider does not support this method")
}

128
vendor/github.com/knadh/koanf/providers/file/file.go generated vendored Normal file
View File

@ -0,0 +1,128 @@
// Package file implements a koanf.Provider that reads raw bytes
// from files on disk to be used with a koanf.Parser to parse
// into conf maps.
package file
import (
"errors"
"fmt"
"io/ioutil"
"path/filepath"
"time"
"github.com/fsnotify/fsnotify"
)
// File implements a File provider.
type File struct {
path string
}
// Provider returns a file provider.
func Provider(path string) *File {
return &File{path: filepath.Clean(path)}
}
// ReadBytes reads the contents of a file on disk and returns the bytes.
func (f *File) ReadBytes() ([]byte, error) {
return ioutil.ReadFile(f.path)
}
// Read is not supported by the file provider.
func (f *File) Read() (map[string]interface{}, error) {
return nil, errors.New("file provider does not support this method")
}
// Watch watches the file and triggers a callback when it changes. It is a
// blocking function that internally spawns a goroutine to watch for changes.
func (f *File) Watch(cb func(event interface{}, err error)) error {
// Resolve symlinks and save the original path so that changes to symlinks
// can be detected.
realPath, err := filepath.EvalSymlinks(f.path)
if err != nil {
return err
}
realPath = filepath.Clean(realPath)
// Although only a single file is being watched, fsnotify has to watch
// the whole parent directory to pick up all events such as symlink changes.
fDir, _ := filepath.Split(f.path)
w, err := fsnotify.NewWatcher()
if err != nil {
return err
}
var (
lastEvent string
lastEventTime time.Time
)
go func() {
loop:
for {
select {
case event, ok := <-w.Events:
if !ok {
cb(nil, errors.New("fsnotify watch channel closed"))
break loop
}
// Use a simple timer to buffer events as certain events fire
// multiple times on some platforms.
if event.String() == lastEvent && time.Since(lastEventTime) < time.Millisecond*5 {
continue
}
lastEvent = event.String()
lastEventTime = time.Now()
evFile := filepath.Clean(event.Name)
// Since the event is triggered on a directory, is this
// one on the file being watched?
if evFile != realPath && evFile != f.path {
continue
}
// The file was removed.
if event.Op&fsnotify.Remove != 0 {
cb(nil, fmt.Errorf("file %s was removed", event.Name))
break loop
}
// Resolve symlink to get the real path, in case the symlink's
// target has changed.
curPath, err := filepath.EvalSymlinks(f.path)
if err != nil {
cb(nil, err)
break loop
}
realPath = filepath.Clean(curPath)
// Finally, we only care about create and write.
if event.Op&(fsnotify.Write|fsnotify.Create) == 0 {
continue
}
// Trigger event.
cb(nil, nil)
// There's an error.
case err, ok := <-w.Errors:
if !ok {
cb(nil, errors.New("fsnotify err channel closed"))
break loop
}
// Pass the error to the callback.
cb(nil, err)
break loop
}
}
w.Close()
}()
// Watch the directory for changes.
return w.Add(fDir)
}

View File

@ -0,0 +1,100 @@
// Package posflag implements a koanf.Provider that reads commandline
// parameters as conf maps using spf13/pflag, a POSIX compliant
// alternative to Go's stdlib flag package.
package posflag
import (
"errors"
"github.com/knadh/koanf"
"github.com/knadh/koanf/maps"
"github.com/spf13/pflag"
)
// Posflag implements a pflag command line provider.
type Posflag struct {
delim string
flagset *pflag.FlagSet
ko *koanf.Koanf
}
// Provider returns a commandline flags provider that returns
// a nested map[string]interface{} of environment variable where the
// nesting hierarchy of keys are defined by delim. For instance, the
// delim "." will convert the key `parent.child.key: 1`
// to `{parent: {child: {key: 1}}}`.
//
// It takes an optional (but recommended) Koanf instance to see if the
// the flags defined have been set from other providers, for instance,
// a config file. If they are not, then the default values of the flags
// are merged. If they do exist, the flag values are not merged but only
// the values that have been explicitly set in the command line are merged.
func Provider(f *pflag.FlagSet, delim string, ko *koanf.Koanf) *Posflag {
return &Posflag{
flagset: f,
delim: delim,
ko: ko,
}
}
// Read reads the flag variables and returns a nested conf map.
func (p *Posflag) Read() (map[string]interface{}, error) {
mp := make(map[string]interface{})
p.flagset.VisitAll(func(f *pflag.Flag) {
// If no value was explicitly set in the command line,
// check if the default value should be used.
if !f.Changed {
if p.ko != nil {
if p.ko.Exists(f.Name) {
return
}
} else {
return
}
}
var v interface{}
switch f.Value.Type() {
case "int":
i, _ := p.flagset.GetInt(f.Name)
v = int64(i)
case "int8":
i, _ := p.flagset.GetInt8(f.Name)
v = int64(i)
case "int16":
i, _ := p.flagset.GetInt16(f.Name)
v = int64(i)
case "int32":
i, _ := p.flagset.GetInt32(f.Name)
v = int64(i)
case "int64":
i, _ := p.flagset.GetInt64(f.Name)
v = int64(i)
case "float32":
v, _ = p.flagset.GetFloat32(f.Name)
case "float":
v, _ = p.flagset.GetFloat64(f.Name)
case "bool":
v, _ = p.flagset.GetBool(f.Name)
case "stringSlice":
v, _ = p.flagset.GetStringSlice(f.Name)
case "intSlice":
v, _ = p.flagset.GetIntSlice(f.Name)
default:
v = f.Value.String()
}
mp[f.Name] = v
})
return maps.Unflatten(mp, p.delim), nil
}
// ReadBytes is not supported by the env koanf.
func (p *Posflag) ReadBytes() ([]byte, error) {
return nil, errors.New("pflag provider does not support this method")
}
// Watch is not supported.
func (p *Posflag) Watch(cb func(event interface{}, err error)) error {
return errors.New("posflag provider does not support this method")
}

9
vendor/github.com/mitchellh/mapstructure/.travis.yml generated vendored Normal file
View File

@ -0,0 +1,9 @@
language: go
go:
- "1.14.x"
- tip
script:
- go test
- go test -bench . -benchmem

61
vendor/github.com/mitchellh/mapstructure/CHANGELOG.md generated vendored Normal file
View File

@ -0,0 +1,61 @@
## 1.3.3
* Decoding maps from maps creates a settable value for decode hooks [GH-203]
## 1.3.2
* Decode into interface type with a struct value is supported [GH-187]
## 1.3.1
* Squash should only squash embedded structs. [GH-194]
## 1.3.0
* Added `",omitempty"` support. This will ignore zero values in the source
structure when encoding. [GH-145]
## 1.2.3
* Fix duplicate entries in Keys list with pointer values. [GH-185]
## 1.2.2
* Do not add unsettable (unexported) values to the unused metadata key
or "remain" value. [GH-150]
## 1.2.1
* Go modules checksum mismatch fix
## 1.2.0
* Added support to capture unused values in a field using the `",remain"` value
in the mapstructure tag. There is an example to showcase usage.
* Added `DecoderConfig` option to always squash embedded structs
* `json.Number` can decode into `uint` types
* Empty slices are preserved and not replaced with nil slices
* Fix panic that can occur in when decoding a map into a nil slice of structs
* Improved package documentation for godoc
## 1.1.2
* Fix error when decode hook decodes interface implementation into interface
type. [GH-140]
## 1.1.1
* Fix panic that can happen in `decodePtr`
## 1.1.0
* Added `StringToIPHookFunc` to convert `string` to `net.IP` and `net.IPNet` [GH-133]
* Support struct to struct decoding [GH-137]
* If source map value is nil, then destination map value is nil (instead of empty)
* If source slice value is nil, then destination slice value is nil (instead of empty)
* If source pointer is nil, then destination pointer is set to nil (instead of
allocated zero value of type)
## 1.0.0
* Initial tagged stable release.

21
vendor/github.com/mitchellh/mapstructure/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Mitchell Hashimoto
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

46
vendor/github.com/mitchellh/mapstructure/README.md generated vendored Normal file
View File

@ -0,0 +1,46 @@
# mapstructure [![Godoc](https://godoc.org/github.com/mitchellh/mapstructure?status.svg)](https://godoc.org/github.com/mitchellh/mapstructure)
mapstructure is a Go library for decoding generic map values to structures
and vice versa, while providing helpful error handling.
This library is most useful when decoding values from some data stream (JSON,
Gob, etc.) where you don't _quite_ know the structure of the underlying data
until you read a part of it. You can therefore read a `map[string]interface{}`
and use this library to decode it into the proper underlying native Go
structure.
## Installation
Standard `go get`:
```
$ go get github.com/mitchellh/mapstructure
```
## Usage & Example
For usage and examples see the [Godoc](http://godoc.org/github.com/mitchellh/mapstructure).
The `Decode` function has examples associated with it there.
## But Why?!
Go offers fantastic standard libraries for decoding formats such as JSON.
The standard method is to have a struct pre-created, and populate that struct
from the bytes of the encoded format. This is great, but the problem is if
you have configuration or an encoding that changes slightly depending on
specific fields. For example, consider this JSON:
```json
{
"type": "person",
"name": "Mitchell"
}
```
Perhaps we can't populate a specific structure without first reading
the "type" field from the JSON. We could always do two passes over the
decoding of the JSON (reading the "type" first, and the rest later).
However, it is much simpler to just decode this into a `map[string]interface{}`
structure, read the "type" key, then use something like this library
to decode it into the proper structure.

View File

@ -0,0 +1,217 @@
package mapstructure
import (
"errors"
"fmt"
"net"
"reflect"
"strconv"
"strings"
"time"
)
// typedDecodeHook takes a raw DecodeHookFunc (an interface{}) and turns
// it into the proper DecodeHookFunc type, such as DecodeHookFuncType.
func typedDecodeHook(h DecodeHookFunc) DecodeHookFunc {
// Create variables here so we can reference them with the reflect pkg
var f1 DecodeHookFuncType
var f2 DecodeHookFuncKind
// Fill in the variables into this interface and the rest is done
// automatically using the reflect package.
potential := []interface{}{f1, f2}
v := reflect.ValueOf(h)
vt := v.Type()
for _, raw := range potential {
pt := reflect.ValueOf(raw).Type()
if vt.ConvertibleTo(pt) {
return v.Convert(pt).Interface()
}
}
return nil
}
// DecodeHookExec executes the given decode hook. This should be used
// since it'll naturally degrade to the older backwards compatible DecodeHookFunc
// that took reflect.Kind instead of reflect.Type.
func DecodeHookExec(
raw DecodeHookFunc,
from reflect.Type, to reflect.Type,
data interface{}) (interface{}, error) {
switch f := typedDecodeHook(raw).(type) {
case DecodeHookFuncType:
return f(from, to, data)
case DecodeHookFuncKind:
return f(from.Kind(), to.Kind(), data)
default:
return nil, errors.New("invalid decode hook signature")
}
}
// ComposeDecodeHookFunc creates a single DecodeHookFunc that
// automatically composes multiple DecodeHookFuncs.
//
// The composed funcs are called in order, with the result of the
// previous transformation.
func ComposeDecodeHookFunc(fs ...DecodeHookFunc) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
var err error
for _, f1 := range fs {
data, err = DecodeHookExec(f1, f, t, data)
if err != nil {
return nil, err
}
// Modify the from kind to be correct with the new data
f = nil
if val := reflect.ValueOf(data); val.IsValid() {
f = val.Type()
}
}
return data, nil
}
}
// StringToSliceHookFunc returns a DecodeHookFunc that converts
// string to []string by splitting on the given sep.
func StringToSliceHookFunc(sep string) DecodeHookFunc {
return func(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
if f != reflect.String || t != reflect.Slice {
return data, nil
}
raw := data.(string)
if raw == "" {
return []string{}, nil
}
return strings.Split(raw, sep), nil
}
}
// StringToTimeDurationHookFunc returns a DecodeHookFunc that converts
// strings to time.Duration.
func StringToTimeDurationHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Duration(5)) {
return data, nil
}
// Convert it by parsing
return time.ParseDuration(data.(string))
}
}
// StringToIPHookFunc returns a DecodeHookFunc that converts
// strings to net.IP
func StringToIPHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IP{}) {
return data, nil
}
// Convert it by parsing
ip := net.ParseIP(data.(string))
if ip == nil {
return net.IP{}, fmt.Errorf("failed parsing ip %v", data)
}
return ip, nil
}
}
// StringToIPNetHookFunc returns a DecodeHookFunc that converts
// strings to net.IPNet
func StringToIPNetHookFunc() DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(net.IPNet{}) {
return data, nil
}
// Convert it by parsing
_, net, err := net.ParseCIDR(data.(string))
return net, err
}
}
// StringToTimeHookFunc returns a DecodeHookFunc that converts
// strings to time.Time.
func StringToTimeHookFunc(layout string) DecodeHookFunc {
return func(
f reflect.Type,
t reflect.Type,
data interface{}) (interface{}, error) {
if f.Kind() != reflect.String {
return data, nil
}
if t != reflect.TypeOf(time.Time{}) {
return data, nil
}
// Convert it by parsing
return time.Parse(layout, data.(string))
}
}
// WeaklyTypedHook is a DecodeHookFunc which adds support for weak typing to
// the decoder.
//
// Note that this is significantly different from the WeaklyTypedInput option
// of the DecoderConfig.
func WeaklyTypedHook(
f reflect.Kind,
t reflect.Kind,
data interface{}) (interface{}, error) {
dataVal := reflect.ValueOf(data)
switch t {
case reflect.String:
switch f {
case reflect.Bool:
if dataVal.Bool() {
return "1", nil
}
return "0", nil
case reflect.Float32:
return strconv.FormatFloat(dataVal.Float(), 'f', -1, 64), nil
case reflect.Int:
return strconv.FormatInt(dataVal.Int(), 10), nil
case reflect.Slice:
dataType := dataVal.Type()
elemKind := dataType.Elem().Kind()
if elemKind == reflect.Uint8 {
return string(dataVal.Interface().([]uint8)), nil
}
case reflect.Uint:
return strconv.FormatUint(dataVal.Uint(), 10), nil
}
}
return data, nil
}

50
vendor/github.com/mitchellh/mapstructure/error.go generated vendored Normal file
View File

@ -0,0 +1,50 @@
package mapstructure
import (
"errors"
"fmt"
"sort"
"strings"
)
// Error implements the error interface and can represents multiple
// errors that occur in the course of a single decode.
type Error struct {
Errors []string
}
func (e *Error) Error() string {
points := make([]string, len(e.Errors))
for i, err := range e.Errors {
points[i] = fmt.Sprintf("* %s", err)
}
sort.Strings(points)
return fmt.Sprintf(
"%d error(s) decoding:\n\n%s",
len(e.Errors), strings.Join(points, "\n"))
}
// WrappedErrors implements the errwrap.Wrapper interface to make this
// return value more useful with the errwrap and go-multierror libraries.
func (e *Error) WrappedErrors() []error {
if e == nil {
return nil
}
result := make([]error, len(e.Errors))
for i, e := range e.Errors {
result[i] = errors.New(e)
}
return result
}
func appendErrors(errors []string, err error) []string {
switch e := err.(type) {
case *Error:
return append(errors, e.Errors...)
default:
return append(errors, e.Error())
}
}

3
vendor/github.com/mitchellh/mapstructure/go.mod generated vendored Normal file
View File

@ -0,0 +1,3 @@
module github.com/mitchellh/mapstructure
go 1.14

1417
vendor/github.com/mitchellh/mapstructure/mapstructure.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

2
vendor/github.com/pelletier/go-toml/.dockerignore generated vendored Normal file
View File

@ -0,0 +1,2 @@
cmd/tomll/tomll
cmd/tomljson/tomljson

5
vendor/github.com/pelletier/go-toml/.gitignore generated vendored Normal file
View File

@ -0,0 +1,5 @@
test_program/test_program_bin
fuzz/
cmd/tomll/tomll
cmd/tomljson/tomljson
cmd/tomltestgen/tomltestgen

132
vendor/github.com/pelletier/go-toml/CONTRIBUTING.md generated vendored Normal file
View File

@ -0,0 +1,132 @@
## Contributing
Thank you for your interest in go-toml! We appreciate you considering
contributing to go-toml!
The main goal is the project is to provide an easy-to-use TOML
implementation for Go that gets the job done and gets out of your way
dealing with TOML is probably not the central piece of your project.
As the single maintainer of go-toml, time is scarce. All help, big or
small, is more than welcomed!
### Ask questions
Any question you may have, somebody else might have it too. Always feel
free to ask them on the [issues tracker][issues-tracker]. We will try to
answer them as clearly and quickly as possible, time permitting.
Asking questions also helps us identify areas where the documentation needs
improvement, or new features that weren't envisioned before. Sometimes, a
seemingly innocent question leads to the fix of a bug. Don't hesitate and
ask away!
### Improve the documentation
The best way to share your knowledge and experience with go-toml is to
improve the documentation. Fix a typo, clarify an interface, add an
example, anything goes!
The documentation is present in the [README][readme] and thorough the
source code. On release, it gets updated on [GoDoc][godoc]. To make a
change to the documentation, create a pull request with your proposed
changes. For simple changes like that, the easiest way to go is probably
the "Fork this project and edit the file" button on Github, displayed at
the top right of the file. Unless it's a trivial change (for example a
typo), provide a little bit of context in your pull request description or
commit message.
### Report a bug
Found a bug! Sorry to hear that :(. Help us and other track them down and
fix by reporting it. [File a new bug report][bug-report] on the [issues
tracker][issues-tracker]. The template should provide enough guidance on
what to include. When in doubt: add more details! By reducing ambiguity and
providing more information, it decreases back and forth and saves everyone
time.
### Code changes
Want to contribute a patch? Very happy to hear that!
First, some high-level rules:
* A short proposal with some POC code is better than a lengthy piece of
text with no code. Code speaks louder than words.
* No backward-incompatible patch will be accepted unless discussed.
Sometimes it's hard, and Go's lack of versioning by default does not
help, but we try not to break people's programs unless we absolutely have
to.
* If you are writing a new feature or extending an existing one, make sure
to write some documentation.
* Bug fixes need to be accompanied with regression tests.
* New code needs to be tested.
* Your commit messages need to explain why the change is needed, even if
already included in the PR description.
It does sound like a lot, but those best practices are here to save time
overall and continuously improve the quality of the project, which is
something everyone benefits from.
#### Get started
The fairly standard code contribution process looks like that:
1. [Fork the project][fork].
2. Make your changes, commit on any branch you like.
3. [Open up a pull request][pull-request]
4. Review, potential ask for changes.
5. Merge. You're in!
Feel free to ask for help! You can create draft pull requests to gather
some early feedback!
#### Run the tests
You can run tests for go-toml using Go's test tool: `go test ./...`.
When creating a pull requests, all tests will be ran on Linux on a few Go
versions (Travis CI), and on Windows using the latest Go version
(AppVeyor).
#### Style
Try to look around and follow the same format and structure as the rest of
the code. We enforce using `go fmt` on the whole code base.
---
### Maintainers-only
#### Merge pull request
Checklist:
* Passing CI.
* Does not introduce backward-incompatible changes (unless discussed).
* Has relevant doc changes.
* Has relevant unit tests.
1. Merge using "squash and merge".
2. Make sure to edit the commit message to keep all the useful information
nice and clean.
3. Make sure the commit title is clear and contains the PR number (#123).
#### New release
1. Go to [releases][releases]. Click on "X commits to master since this
release".
2. Make note of all the changes. Look for backward incompatible changes,
new features, and bug fixes.
3. Pick the new version using the above and semver.
4. Create a [new release][new-release].
5. Follow the same format as [1.1.0][release-110].
[issues-tracker]: https://github.com/pelletier/go-toml/issues
[bug-report]: https://github.com/pelletier/go-toml/issues/new?template=bug_report.md
[godoc]: https://godoc.org/github.com/pelletier/go-toml
[readme]: ./README.md
[fork]: https://help.github.com/articles/fork-a-repo
[pull-request]: https://help.github.com/en/articles/creating-a-pull-request
[releases]: https://github.com/pelletier/go-toml/releases
[new-release]: https://github.com/pelletier/go-toml/releases/new
[release-110]: https://github.com/pelletier/go-toml/releases/tag/v1.1.0

11
vendor/github.com/pelletier/go-toml/Dockerfile generated vendored Normal file
View File

@ -0,0 +1,11 @@
FROM golang:1.12-alpine3.9 as builder
WORKDIR /go/src/github.com/pelletier/go-toml
COPY . .
ENV CGO_ENABLED=0
ENV GOOS=linux
RUN go install ./...
FROM scratch
COPY --from=builder /go/bin/tomll /usr/bin/tomll
COPY --from=builder /go/bin/tomljson /usr/bin/tomljson
COPY --from=builder /go/bin/jsontoml /usr/bin/jsontoml

View File

@ -1,6 +1,6 @@
MIT License
The MIT License (MIT)
Copyright (c) 2018 Denis Dyakov
Copyright (c) 2013 - 2017 Thomas Pelletier, Eric Anderton
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

29
vendor/github.com/pelletier/go-toml/Makefile generated vendored Normal file
View File

@ -0,0 +1,29 @@
export CGO_ENABLED=0
go := go
go.goos ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f1)
go.goarch ?= $(shell echo `go version`|cut -f4 -d ' '|cut -d '/' -f2)
out.tools := tomll tomljson jsontoml
out.dist := $(out.tools:=_$(go.goos)_$(go.goarch).tar.xz)
sources := $(wildcard **/*.go)
.PHONY:
tools: $(out.tools)
$(out.tools): $(sources)
GOOS=$(go.goos) GOARCH=$(go.goarch) $(go) build ./cmd/$@
.PHONY:
dist: $(out.dist)
$(out.dist):%_$(go.goos)_$(go.goarch).tar.xz: %
if [ "$(go.goos)" = "windows" ]; then \
tar -cJf $@ $^.exe; \
else \
tar -cJf $@ $^; \
fi
.PHONY:
clean:
rm -rf $(out.tools) $(out.dist)

View File

@ -0,0 +1,5 @@
**Issue:** add link to pelletier/go-toml issue here
Explanation of what this pull request does.
More detailed description of the decisions being made and the reasons why (if the patch is non-trivial).

151
vendor/github.com/pelletier/go-toml/README.md generated vendored Normal file
View File

@ -0,0 +1,151 @@
# go-toml
Go library for the [TOML](https://github.com/mojombo/toml) format.
This library supports TOML version
[v1.0.0-rc.1](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v1.0.0-rc.1.md)
[![GoDoc](https://godoc.org/github.com/pelletier/go-toml?status.svg)](http://godoc.org/github.com/pelletier/go-toml)
[![license](https://img.shields.io/github/license/pelletier/go-toml.svg)](https://github.com/pelletier/go-toml/blob/master/LICENSE)
[![Build Status](https://dev.azure.com/pelletierthomas/go-toml-ci/_apis/build/status/pelletier.go-toml?branchName=master)](https://dev.azure.com/pelletierthomas/go-toml-ci/_build/latest?definitionId=1&branchName=master)
[![codecov](https://codecov.io/gh/pelletier/go-toml/branch/master/graph/badge.svg)](https://codecov.io/gh/pelletier/go-toml)
[![Go Report Card](https://goreportcard.com/badge/github.com/pelletier/go-toml)](https://goreportcard.com/report/github.com/pelletier/go-toml)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fpelletier%2Fgo-toml?ref=badge_shield)
## Features
Go-toml provides the following features for using data parsed from TOML documents:
* Load TOML documents from files and string data
* Easily navigate TOML structure using Tree
* Marshaling and unmarshaling to and from data structures
* Line & column position data for all parsed elements
* [Query support similar to JSON-Path](query/)
* Syntax errors contain line and column numbers
## Import
```go
import "github.com/pelletier/go-toml"
```
## Usage example
Read a TOML document:
```go
config, _ := toml.Load(`
[postgres]
user = "pelletier"
password = "mypassword"`)
// retrieve data directly
user := config.Get("postgres.user").(string)
// or using an intermediate object
postgresConfig := config.Get("postgres").(*toml.Tree)
password := postgresConfig.Get("password").(string)
```
Or use Unmarshal:
```go
type Postgres struct {
User string
Password string
}
type Config struct {
Postgres Postgres
}
doc := []byte(`
[Postgres]
User = "pelletier"
Password = "mypassword"`)
config := Config{}
toml.Unmarshal(doc, &config)
fmt.Println("user=", config.Postgres.User)
```
Or use a query:
```go
// use a query to gather elements without walking the tree
q, _ := query.Compile("$..[user,password]")
results := q.Execute(config)
for ii, item := range results.Values() {
fmt.Printf("Query result %d: %v\n", ii, item)
}
```
## Documentation
The documentation and additional examples are available at
[godoc.org](http://godoc.org/github.com/pelletier/go-toml).
## Tools
Go-toml provides two handy command line tools:
* `tomll`: Reads TOML files and lints them.
```
go install github.com/pelletier/go-toml/cmd/tomll
tomll --help
```
* `tomljson`: Reads a TOML file and outputs its JSON representation.
```
go install github.com/pelletier/go-toml/cmd/tomljson
tomljson --help
```
* `jsontoml`: Reads a JSON file and outputs a TOML representation.
```
go install github.com/pelletier/go-toml/cmd/jsontoml
jsontoml --help
```
### Docker image
Those tools are also availble as a Docker image from
[dockerhub](https://hub.docker.com/r/pelletier/go-toml). For example, to
use `tomljson`:
```
docker run -v $PWD:/workdir pelletier/go-toml tomljson /workdir/example.toml
```
Only master (`latest`) and tagged versions are published to dockerhub. You
can build your own image as usual:
```
docker build -t go-toml .
```
## Contribute
Feel free to report bugs and patches using GitHub's pull requests system on
[pelletier/go-toml](https://github.com/pelletier/go-toml). Any feedback would be
much appreciated!
### Run tests
`go test ./...`
### Fuzzing
The script `./fuzz.sh` is available to
run [go-fuzz](https://github.com/dvyukov/go-fuzz) on go-toml.
## Versioning
Go-toml follows [Semantic Versioning](http://semver.org/). The supported version
of [TOML](https://github.com/toml-lang/toml) is indicated at the beginning of
this document. The last two major versions of Go are supported
(see [Go Release Policy](https://golang.org/doc/devel/release.html#policy)).
## License
The MIT License (MIT). Read [LICENSE](LICENSE).

230
vendor/github.com/pelletier/go-toml/azure-pipelines.yml generated vendored Normal file
View File

@ -0,0 +1,230 @@
trigger:
- master
stages:
- stage: fuzzit
displayName: "Run Fuzzit"
dependsOn: []
condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'))
jobs:
- job: submit
displayName: "Submit"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.15"
inputs:
version: "1.15"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
- task: Bash@3
inputs:
filePath: './fuzzit.sh'
env:
TYPE: fuzzing
FUZZIT_API_KEY: $(FUZZIT_API_KEY)
- stage: run_checks
displayName: "Check"
dependsOn: []
jobs:
- job: fmt
displayName: "fmt"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.15"
inputs:
version: "1.15"
- task: Go@0
displayName: "go fmt ./..."
inputs:
command: 'custom'
customCommand: 'fmt'
arguments: './...'
- job: coverage
displayName: "coverage"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.15"
inputs:
version: "1.15"
- task: Go@0
displayName: "Generate coverage"
inputs:
command: 'test'
arguments: "-race -coverprofile=coverage.txt -covermode=atomic"
- task: Bash@3
inputs:
targetType: 'inline'
script: 'bash <(curl -s https://codecov.io/bash) -t ${CODECOV_TOKEN}'
env:
CODECOV_TOKEN: $(CODECOV_TOKEN)
- job: benchmark
displayName: "benchmark"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.15"
inputs:
version: "1.15"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- task: Bash@3
inputs:
filePath: './benchmark.sh'
arguments: "master $(Build.Repository.Uri)"
- job: fuzzing
displayName: "fuzzing"
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go 1.15"
inputs:
version: "1.15"
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
- script: mkdir -p ${HOME}/go/src/github.com/pelletier/go-toml
- script: cp -R . ${HOME}/go/src/github.com/pelletier/go-toml
- task: Bash@3
inputs:
filePath: './fuzzit.sh'
env:
TYPE: local-regression
- job: go_unit_tests
displayName: "unit tests"
strategy:
matrix:
linux 1.15:
goVersion: '1.15'
imageName: 'ubuntu-latest'
mac 1.15:
goVersion: '1.15'
imageName: 'macOS-latest'
windows 1.15:
goVersion: '1.15'
imageName: 'windows-latest'
linux 1.14:
goVersion: '1.14'
imageName: 'ubuntu-latest'
mac 1.14:
goVersion: '1.14'
imageName: 'macOS-latest'
windows 1.14:
goVersion: '1.14'
imageName: 'windows-latest'
pool:
vmImage: $(imageName)
steps:
- task: GoTool@0
displayName: "Install Go $(goVersion)"
inputs:
version: $(goVersion)
- task: Go@0
displayName: "go test ./..."
inputs:
command: 'test'
arguments: './...'
- stage: build_binaries
displayName: "Build binaries"
dependsOn: run_checks
jobs:
- job: build_binary
displayName: "Build binary"
strategy:
matrix:
linux_amd64:
GOOS: linux
GOARCH: amd64
darwin_amd64:
GOOS: darwin
GOARCH: amd64
windows_amd64:
GOOS: windows
GOARCH: amd64
pool:
vmImage: ubuntu-latest
steps:
- task: GoTool@0
displayName: "Install Go"
inputs:
version: 1.15
- task: Bash@3
inputs:
targetType: inline
script: "make dist"
env:
go.goos: $(GOOS)
go.goarch: $(GOARCH)
- task: CopyFiles@2
inputs:
sourceFolder: '$(Build.SourcesDirectory)'
contents: '*.tar.xz'
TargetFolder: '$(Build.ArtifactStagingDirectory)'
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: binaries
- stage: build_binaries_manifest
displayName: "Build binaries manifest"
dependsOn: build_binaries
jobs:
- job: build_manifest
displayName: "Build binaries manifest"
steps:
- task: DownloadBuildArtifacts@0
inputs:
buildType: 'current'
downloadType: 'single'
artifactName: 'binaries'
downloadPath: '$(Build.SourcesDirectory)'
- task: Bash@3
inputs:
targetType: inline
script: "cd binaries && sha256sum --binary *.tar.xz | tee $(Build.ArtifactStagingDirectory)/sha256sums.txt"
- task: PublishBuildArtifacts@1
inputs:
pathtoPublish: '$(Build.ArtifactStagingDirectory)'
artifactName: manifest
- stage: build_docker_image
displayName: "Build Docker image"
dependsOn: run_checks
jobs:
- job: build
displayName: "Build"
pool:
vmImage: ubuntu-latest
steps:
- task: Docker@2
inputs:
command: 'build'
Dockerfile: 'Dockerfile'
buildContext: '.'
addPipelineData: false
- stage: publish_docker_image
displayName: "Publish Docker image"
dependsOn: build_docker_image
condition: and(succeeded(), eq(variables['Build.SourceBranchName'], 'master'))
jobs:
- job: publish
displayName: "Publish"
pool:
vmImage: ubuntu-latest
steps:
- task: Docker@2
inputs:
containerRegistry: 'DockerHub'
repository: 'pelletier/go-toml'
command: 'buildAndPush'
Dockerfile: 'Dockerfile'
buildContext: '.'
tags: 'latest'

35
vendor/github.com/pelletier/go-toml/benchmark.sh generated vendored Normal file
View File

@ -0,0 +1,35 @@
#!/bin/bash
set -ex
reference_ref=${1:-master}
reference_git=${2:-.}
if ! `hash benchstat 2>/dev/null`; then
echo "Installing benchstat"
go get golang.org/x/perf/cmd/benchstat
fi
tempdir=`mktemp -d /tmp/go-toml-benchmark-XXXXXX`
ref_tempdir="${tempdir}/ref"
ref_benchmark="${ref_tempdir}/benchmark-`echo -n ${reference_ref}|tr -s '/' '-'`.txt"
local_benchmark="`pwd`/benchmark-local.txt"
echo "=== ${reference_ref} (${ref_tempdir})"
git clone ${reference_git} ${ref_tempdir} >/dev/null 2>/dev/null
pushd ${ref_tempdir} >/dev/null
git checkout ${reference_ref} >/dev/null 2>/dev/null
go test -bench=. -benchmem | tee ${ref_benchmark}
cd benchmark
go test -bench=. -benchmem | tee -a ${ref_benchmark}
popd >/dev/null
echo ""
echo "=== local"
go test -bench=. -benchmem | tee ${local_benchmark}
cd benchmark
go test -bench=. -benchmem | tee -a ${local_benchmark}
echo ""
echo "=== diff"
benchstat -delta-test=none ${ref_benchmark} ${local_benchmark}

23
vendor/github.com/pelletier/go-toml/doc.go generated vendored Normal file
View File

@ -0,0 +1,23 @@
// Package toml is a TOML parser and manipulation library.
//
// This version supports the specification as described in
// https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md
//
// Marshaling
//
// Go-toml can marshal and unmarshal TOML documents from and to data
// structures.
//
// TOML document as a tree
//
// Go-toml can operate on a TOML document as a tree. Use one of the Load*
// functions to parse TOML data and obtain a Tree instance, then one of its
// methods to manipulate the tree.
//
// JSONPath-like queries
//
// The package github.com/pelletier/go-toml/query implements a system
// similar to JSONPath to quickly retrieve elements of a TOML document using a
// single expression. See the package documentation for more information.
//
package toml

30
vendor/github.com/pelletier/go-toml/example-crlf.toml generated vendored Normal file
View File

@ -0,0 +1,30 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported

30
vendor/github.com/pelletier/go-toml/example.toml generated vendored Normal file
View File

@ -0,0 +1,30 @@
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true
[servers]
# You can indent as you please. Tabs or spaces. TOML don't care.
[servers.alpha]
ip = "10.0.0.1"
dc = "eqdc10"
[servers.beta]
ip = "10.0.0.2"
dc = "eqdc10"
[clients]
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
score = 4e-08 # to make sure leading zeroes in exponent parts of floats are supported

31
vendor/github.com/pelletier/go-toml/fuzz.go generated vendored Normal file
View File

@ -0,0 +1,31 @@
// +build gofuzz
package toml
func Fuzz(data []byte) int {
tree, err := LoadBytes(data)
if err != nil {
if tree != nil {
panic("tree must be nil if there is an error")
}
return 0
}
str, err := tree.ToTomlString()
if err != nil {
if str != "" {
panic(`str must be "" if there is an error`)
}
panic(err)
}
tree, err = Load(str)
if err != nil {
if tree != nil {
panic("tree must be nil if there is an error")
}
return 0
}
return 1
}

15
vendor/github.com/pelletier/go-toml/fuzz.sh generated vendored Normal file
View File

@ -0,0 +1,15 @@
#! /bin/sh
set -eu
go get github.com/dvyukov/go-fuzz/go-fuzz
go get github.com/dvyukov/go-fuzz/go-fuzz-build
if [ ! -e toml-fuzz.zip ]; then
go-fuzz-build github.com/pelletier/go-toml
fi
rm -fr fuzz
mkdir -p fuzz/corpus
cp *.toml fuzz/corpus
go-fuzz -bin=toml-fuzz.zip -workdir=fuzz

Some files were not shown because too many files have changed in this diff Show More