Compare commits
43 Commits
20200903-1
...
master
Author | SHA1 | Date |
---|---|---|
KemoNine | f2292f3099 | |
KemoNine | d335e1f85a | |
KemoNine | 39451ec3b3 | |
KemoNine | 35e0032826 | |
KemoNine | 0c9bfd6a7c | |
KemoNine | 0579b3e97d | |
KemoNine | df886ff66d | |
kemonine | 0c2c136413 | |
kemonine | ab925c4b2c | |
kemonine | 2ffda69ca8 | |
KemoNine | d5a06b79db | |
KemoNine | be99f9ae41 | |
KemoNine | 4b0d611715 | |
KemoNine | 97a3474bd2 | |
KemoNine | 9349b91819 | |
KemoNine | 13d019cf62 | |
KemoNine | c2a21eabd9 | |
KemoNine | 20fab2ade0 | |
KemoNine | 1ccbf4ee03 | |
KemoNine | e979318132 | |
KemoNine | 98d5fdce2f | |
KemoNine | 588145b163 | |
KemoNine | 6634e7d8ef | |
KemoNine | f8b4031aa3 | |
KemoNine | be7c3e63a2 | |
KemoNine | 3ca4b66304 | |
KemoNine | 5eab31f093 | |
KemoNine | 7afb3f17ee | |
KemoNine | 728c281ad3 | |
KemoNine | 15dfaa31f9 | |
KemoNine | 769029b9c2 | |
KemoNine | e326984ca6 | |
KemoNine | a33f8b7539 | |
KemoNine | b0764b684a | |
KemoNine | 20c73767de | |
KemoNine | bfbb296784 | |
KemoNine | 80828aa021 | |
KemoNine | 6c6bbe02fe | |
KemoNine | 638bd93675 | |
KemoNine | 592a152860 | |
KemoNine | 0e5d316731 | |
KemoNine | fffd67f8c0 | |
KemoNine | 1b5d77e07a |
12
.drone.yml
12
.drone.yml
|
@ -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/gui cmd/gui/gui.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
|
||||
|
|
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -4,25 +4,35 @@ This is the main source code for the custom UI used by PiFrame. This is responsi
|
|||
|
||||
## Config
|
||||
|
||||
The GUI will work off a config similar to the following. Use the ```generate config``` option to generate a default configuration in ```/etc/default/pf.toml```
|
||||
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.
|
||||
|
||||
```
|
||||
|
||||
[slideshow]
|
||||
duration = "300s"
|
||||
|
||||
[hdmi]
|
||||
# These are SYSTEMD.TIME formatted times
|
||||
# You probably want to just adjust the actual times here
|
||||
# See https://www.freedesktop.org/software/systemd/man/systemd.time.html
|
||||
off = "*-*-* 00:00:00"
|
||||
on = "*-*-* 06:00:00"
|
||||
config-ui-only = false
|
||||
|
||||
[albums]
|
||||
root = "/tank/pictures"
|
||||
selected = [
|
||||
"/",
|
||||
"/KemoNine"
|
||||
]
|
||||
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"
|
||||
|
||||
|
||||
```
|
||||
|
|
|
@ -4,38 +4,16 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/knadh/koanf"
|
||||
"github.com/knadh/koanf/parsers/toml"
|
||||
"github.com/knadh/koanf/providers/confmap"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
"github.com/knadh/koanf/providers/posflag"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
"git.kemonine.info/PiFrame/config"
|
||||
"git.kemonine.info/PiFrame/ui"
|
||||
)
|
||||
|
||||
const (
|
||||
CLI_FLAG_CONFIG_ONLY = "config-ui-only"
|
||||
)
|
||||
|
||||
const (
|
||||
CONFIG_FILE_PATH = "/etc/default/pf.toml"
|
||||
CONFIG_KEY_SLIDESHOW_DURATION = "slideshow.duration"
|
||||
CONFIG_KEY_HDMI_OFF = "hdmi.off"
|
||||
CONFIG_KEY_HDMI_ON = "hdmi.on"
|
||||
CONFIG_KEY_ALBUMS_ROOT = "albums.root"
|
||||
CONFIG_KEY_ALBUMS_SELECTED = "albums.selected"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_SLIDESHOW_DURATION = "300s"
|
||||
DEFAULT_HDMI_OFF = "*-*-* 00:00:00"
|
||||
DEFAULT_HDMI_ON = "*-*-* 06:00:00"
|
||||
DEFAULT_ALBUMS_ROOT = "/tank/pictures"
|
||||
DEFAULT_ALBUM_SELECTED = "/"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Command line flag handler
|
||||
f := flag.NewFlagSet("piframe", flag.ContinueOnError)
|
||||
|
@ -44,38 +22,16 @@ func main() {
|
|||
os.Exit(0)
|
||||
}
|
||||
// Command line flags
|
||||
f.Bool(CLI_FLAG_CONFIG_ONLY, false, "Only show the config UI, NOT the slideshow")
|
||||
cliFlag := f.Lookup(CLI_FLAG_CONFIG_ONLY)
|
||||
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:])
|
||||
|
||||
// Main config variable
|
||||
var pfConfig = koanf.New(".")
|
||||
|
||||
// Setup some defaults
|
||||
pfConfig.Load(confmap.Provider(map[string]interface{}{
|
||||
CONFIG_KEY_SLIDESHOW_DURATION: DEFAULT_SLIDESHOW_DURATION,
|
||||
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: []string{DEFAULT_ALBUM_SELECTED},
|
||||
}, "."), nil)
|
||||
|
||||
// Bring in /etc/defaults/pf.toml if it exists
|
||||
configFileProvider := file.Provider(CONFIG_FILE_PATH)
|
||||
_, err := os.Stat(CONFIG_FILE_PATH)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// log.Printf("%s does not exist, USING DEFAULTS", CONFIG_FILE_PATH)
|
||||
} else {
|
||||
if errConfigFile := pfConfig.Load(configFileProvider, toml.Parser()); errConfigFile != nil {
|
||||
log.Fatalf("Error loading config : %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
|
@ -84,17 +40,28 @@ func main() {
|
|||
return
|
||||
}
|
||||
|
||||
pfConfig.Load(configFileProvider, toml.Parser())
|
||||
// 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 : %v", err)
|
||||
log.Fatalf("Error loading command line flags : %s", err)
|
||||
}
|
||||
|
||||
if !pfConfig.Bool(CLI_FLAG_CONFIG_ONLY) {
|
||||
ui.Slideshow()
|
||||
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()
|
||||
ui.ConfigGui(pfConfig)
|
||||
}
|
||||
|
|
|
@ -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.
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -7,16 +7,32 @@ 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() {
|
||||
// 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", "pf-ui.service").Run()
|
||||
|
@ -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:
|
||||
|
|
|
@ -90,7 +90,6 @@ 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 old wifi configs and apply new one
|
||||
nmWifi := wifi.New(essid, password)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# config
|
||||
|
||||
Mini module for working with PiFrame configuration 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
|
||||
}
|
|
@ -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,
|
||||
}
|
15
go.mod
15
go.mod
|
@ -3,18 +3,17 @@ module git.kemonine.info/PiFrame
|
|||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/d2r2/go-bh1750 v0.0.0-20181222061755-1195122364ab // indirect
|
||||
github.com/d2r2/go-i2c v0.0.0-20191123181816-73a8a799d6bc // indirect
|
||||
github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e // indirect
|
||||
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/eiannone/keyboard v0.0.0-20200508000154-caf4b762e807
|
||||
github.com/fsnotify/fsnotify v1.4.9 // indirect
|
||||
github.com/gdamore/tcell v1.4.0
|
||||
github.com/guillermo/go.procmeminfo v0.0.0-20131127224636-be4355a9fb0e
|
||||
github.com/knadh/koanf v0.12.0
|
||||
github.com/mackerelio/go-osstat v0.1.0 // indirect
|
||||
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9
|
||||
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
|
||||
)
|
||||
|
|
192
go.sum
192
go.sum
|
@ -1,7 +1,25 @@
|
|||
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=
|
||||
|
@ -9,11 +27,13 @@ github.com/d2r2/go-logger v0.0.0-20181221090742-9998a510495e/go.mod h1:oA+9PUt8F
|
|||
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=
|
||||
|
@ -21,46 +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/mackerelio/go-osstat v0.1.0 h1:e57QHeHob8kKJ5FhcXGdzx5O6Ktuc5RHMDIkeqhgkFA=
|
||||
github.com/mackerelio/go-osstat v0.1.0/go.mod h1:1K3NeYLhMHPvzUu+ePYXtoB58wkaRpxZsGClZBJyIFw=
|
||||
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=
|
||||
golang.org/x/sys v0.0.0-20190410235845-0ad05ae3009d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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=
|
||||
|
|
281
ui/config.go
281
ui/config.go
|
@ -1,46 +1,65 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"net"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"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"
|
||||
CMD_VCGENCMD = "/opt/vc/bin/vcgencmd"
|
||||
FILE_CPU_TEMP = "/sys/class/thermal/thermal_zone0/temp"
|
||||
ALBUM_ROOT_DIR = "/tank/pictures/"
|
||||
SYNCTHING_FOLDER_SKIP = ".stfolder"
|
||||
)
|
||||
|
||||
const (
|
||||
PAGE_MAIN_UI = "PAGE_MAIN_UI"
|
||||
PAGE_EXIT = "PAGE_EXIT"
|
||||
PAGE_REBOOT = "PAGE_REBOOT"
|
||||
PAGE_POWEROFF = "PAGE_POWEROFF"
|
||||
PAGE_MAIN_UI = "PAGE_MAIN_UI"
|
||||
PAGE_SAVE_EXIT = "PAGE_SAVE_EXIT"
|
||||
PAGE_EXIT = "PAGE_EXIT"
|
||||
PAGE_REBOOT = "PAGE_REBOOT"
|
||||
PAGE_POWEROFF = "PAGE_POWEROFF"
|
||||
)
|
||||
|
||||
func ConfigGui() {
|
||||
// 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.Printf("Error getting memory info : %s", err)
|
||||
log.Fatalf("Error getting memory info : %s", err)
|
||||
}
|
||||
|
||||
// Network interfaces for status panel
|
||||
|
@ -60,27 +79,14 @@ func ConfigGui() {
|
|||
filesystems := strings.Split(strings.Trim(string(findmntOut), "\n"), "\n")
|
||||
|
||||
// GPU Temp
|
||||
vcgencmdOut, err := exec.Command(CMD_VCGENCMD, "measure_temp").Output()
|
||||
if err != nil {
|
||||
log.Fatalf("Error getting GPU temp : %s", err)
|
||||
}
|
||||
gpuTemp := strings.Split(strings.Trim(string(vcgencmdOut), "\n"), "=")[1]
|
||||
gpuTemp := fmt.Sprintf("%.2f'C", utils.GetGPUTemp())
|
||||
|
||||
// CPU Temp
|
||||
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)
|
||||
}
|
||||
cpuTemp := fmt.Sprintf("%.2f'C", float64(cpuTempInt)/1000.0)
|
||||
cpuTemp := fmt.Sprintf("%.2f'C", utils.GetCPUTemp())
|
||||
|
||||
// Get list of all folders that can be used as albums
|
||||
var albums []string
|
||||
err = filepath.Walk(ALBUM_ROOT_DIR, func(path string, fi os.FileInfo, err error) error {
|
||||
err = filepath.Walk(pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT), func(path string, fi os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -88,7 +94,7 @@ func ConfigGui() {
|
|||
if strings.Contains(path, SYNCTHING_FOLDER_SKIP) {
|
||||
return nil
|
||||
}
|
||||
albumName := strings.TrimPrefix(path, ALBUM_ROOT_DIR)
|
||||
albumName := strings.TrimPrefix(path, pfconfig.String(config.CONFIG_KEY_ALBUMS_ROOT))
|
||||
if albumName == "" {
|
||||
albumName = "Main Folder"
|
||||
}
|
||||
|
@ -101,7 +107,7 @@ func ConfigGui() {
|
|||
}
|
||||
|
||||
// Run config UI when slideshow stops
|
||||
app := tview.NewApplication()
|
||||
app = tview.NewApplication()
|
||||
|
||||
// Header
|
||||
headerTitle := tview.NewTextView().
|
||||
|
@ -119,12 +125,20 @@ func ConfigGui() {
|
|||
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.ColorGreen).
|
||||
SetBackgroundColor(tcell.ColorYellow).
|
||||
SetRect(0, 0, 22, 3)
|
||||
|
||||
rebootButton := tview.NewButton("Reboot").
|
||||
|
@ -132,7 +146,7 @@ func ConfigGui() {
|
|||
rebootButton.SetLabelColor(tcell.ColorBlack).
|
||||
SetBorder(true).
|
||||
SetBorderColor(tcell.ColorBlack).
|
||||
SetBackgroundColor(tcell.ColorYellow).
|
||||
SetBackgroundColor(tcell.ColorFuchsia).
|
||||
SetRect(0, 0, 22, 3)
|
||||
|
||||
powerOffButton := tview.NewButton("Power Off").
|
||||
|
@ -145,19 +159,23 @@ func ConfigGui() {
|
|||
|
||||
// Footer
|
||||
footer := tview.NewFlex()
|
||||
footer.AddItem(exitButton, 0, 1, false).
|
||||
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 = tview.NewList()
|
||||
menu.SetBorder(true).
|
||||
SetTitle("Menu").
|
||||
SetTitleColor(tcell.ColorAqua)
|
||||
menu.AddItem("Select Albums", "", '1', nil)
|
||||
menu.AddItem("Configure WiFi", "", '2', 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().
|
||||
main = tview.NewFlex().
|
||||
SetDirection(tview.FlexRow)
|
||||
|
||||
// Setup main panel (Center column)
|
||||
|
@ -165,6 +183,42 @@ func ConfigGui() {
|
|||
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 := ""
|
||||
|
@ -179,22 +233,26 @@ func ConfigGui() {
|
|||
// Cleanup old wifi configs and apply new one
|
||||
nmWifi := wifi.New(wifiConfigAccessPoint, wifiConfigPassword)
|
||||
nmWifi.ApplyConfig()
|
||||
resetMain()
|
||||
})
|
||||
wifiConfigForm.AddButton("Cancel", func() {
|
||||
main.Clear()
|
||||
app.SetFocus(menu)
|
||||
})
|
||||
wifiConfigForm.AddButton("Cancel", resetMain)
|
||||
|
||||
// Select Albums Form
|
||||
selectAlbumsForm := tview.NewForm()
|
||||
for _, album := range albums {
|
||||
selectAlbumsForm.AddCheckbox(album, true, nil)
|
||||
}
|
||||
selectAlbumsForm.AddButton("Apply", nil)
|
||||
selectAlbumsForm.AddButton("Cancel", func() {
|
||||
main.Clear()
|
||||
app.SetFocus(menu)
|
||||
// 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) {
|
||||
|
@ -204,12 +262,33 @@ func ConfigGui() {
|
|||
main.AddItem(selectAlbumsForm, 0, 1, true)
|
||||
app.SetFocus(selectAlbumsForm)
|
||||
}
|
||||
if title == "Configure WiFi" {
|
||||
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
|
||||
|
@ -252,7 +331,6 @@ func ConfigGui() {
|
|||
for _, i := range ifaces {
|
||||
addrs, err := i.Addrs()
|
||||
if err != nil {
|
||||
log.Printf("Error getting interface addresses : %s", err)
|
||||
continue
|
||||
}
|
||||
for _, a := range addrs {
|
||||
|
@ -303,6 +381,94 @@ func ConfigGui() {
|
|||
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"}).
|
||||
|
@ -352,7 +518,7 @@ func ConfigGui() {
|
|||
})
|
||||
|
||||
// Setup tracking of which are of the UI can/has focus
|
||||
primitivesThatCanFocus := []tview.Primitive{menu, exitButton, rebootButton, powerOffButton}
|
||||
primitivesThatCanFocus := []tview.Primitive{menu, saveExitButton, exitButton, rebootButton, powerOffButton}
|
||||
currentFocus := 0
|
||||
|
||||
// Setup basic switching between main menu and buttons for the UI
|
||||
|
@ -364,9 +530,16 @@ func ConfigGui() {
|
|||
// 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)
|
||||
wifiField, wifiButton := wifiConfigForm.GetFocusedItemIndex()
|
||||
albumField, albumButton := selectAlbumsForm.GetFocusedItemIndex()
|
||||
if wifiField != -1 || wifiButton != -1 || albumField != -1 || albumButton != -1 {
|
||||
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())
|
||||
|
@ -417,6 +590,6 @@ func ConfigGui() {
|
|||
|
||||
// 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 : ", err)
|
||||
log.Fatalf("Failed to run UI : %s", err)
|
||||
}
|
||||
}
|
||||
|
|
179
ui/slideshow.go
179
ui/slideshow.go
|
@ -3,36 +3,136 @@ 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 (
|
||||
CMD_FIM = "/usr/local/bin/pf-fim.sh"
|
||||
SLIDESHOW_INTERVAL = 300 * time.Second
|
||||
PATH_TEMP_FOR_SLIDESHOW = "/run/piframe/fim"
|
||||
)
|
||||
|
||||
func Slideshow() {
|
||||
// fim placeholder so we can operate on it when a exit slideshow is received
|
||||
var fim *exec.Cmd = nil
|
||||
// fim placeholders so we can reset them as needed
|
||||
var fim *exec.Cmd = nil
|
||||
var stdin io.WriteCloser = nil
|
||||
|
||||
// Run slideshow
|
||||
fim = exec.Command(CMD_FIM)
|
||||
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
|
||||
stdin, err := fim.StdinPipe()
|
||||
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()
|
||||
ticker := time.NewTicker(SLIDESHOW_INTERVAL)
|
||||
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 {
|
||||
|
@ -57,6 +157,15 @@ func Slideshow() {
|
|||
}
|
||||
// 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) {
|
||||
|
@ -68,7 +177,6 @@ func Slideshow() {
|
|||
if event.Err != nil {
|
||||
log.Fatalf("Error listening to key events : %s", err)
|
||||
}
|
||||
log.Printf("You pressed: key %X\n", event.Key)
|
||||
|
||||
// Keys for fim event management (previous/next in particular)
|
||||
fimKey := ""
|
||||
|
@ -81,6 +189,8 @@ func Slideshow() {
|
|||
|
||||
// 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 {
|
||||
|
@ -88,35 +198,66 @@ func Slideshow() {
|
|||
log.Fatalf("failed to kill fim : %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Control fim if we received a valid key for next/previous slide
|
||||
if fimKey != "" {
|
||||
log.Printf("Sending key to fim : %s", fimKey)
|
||||
_, err = io.WriteString(stdin, fimKey)
|
||||
if err != nil {
|
||||
log.Fatalf("Error controlling fim : %s", err)
|
||||
}
|
||||
ticker.Reset(SLIDESHOW_INTERVAL)
|
||||
ticker.Reset(pfconfig.Duration(config.CONFIG_KEY_SLIDESHOW_INTERVAL))
|
||||
}
|
||||
}
|
||||
}
|
||||
}(keyboardCtx)
|
||||
|
||||
// Run fim
|
||||
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 running fim : %s", err)
|
||||
// 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()
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# Utils
|
||||
|
||||
Misc utilities used elsewhere by PiFrame go code.
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
`
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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.
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
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 crossbuild-essential-armel # 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
|
||||
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2016 Denis Dyakov
|
||||
Copyright (c) 2013 Dave Cheney
|
||||
|
||||
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.
|
|
@ -0,0 +1,97 @@
|
|||
I2C-bus interaction of peripheral sensors with Raspberry PI embedded linux or respective clones
|
||||
==============================================================================================
|
||||
|
||||
[![Build Status](https://travis-ci.org/d2r2/go-i2c.svg?branch=master)](https://travis-ci.org/d2r2/go-i2c)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/d2r2/go-i2c)](https://goreportcard.com/report/github.com/d2r2/go-i2c)
|
||||
[![GoDoc](https://godoc.org/github.com/d2r2/go-i2c?status.svg)](https://godoc.org/github.com/d2r2/go-i2c)
|
||||
[![MIT License](http://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
|
||||
|
||||
This library written in [Go programming language](https://golang.org/) intended to activate and interact with the I2C bus by reading and writing data.
|
||||
|
||||
Compatibility
|
||||
-------------
|
||||
|
||||
Tested on Raspberry Pi 1 (model B), Raspberry Pi 3 B+, Banana Pi (model M1), Orange Pi Zero, Orange Pi One.
|
||||
|
||||
Golang usage
|
||||
------------
|
||||
|
||||
```go
|
||||
func main() {
|
||||
// Create new connection to I2C bus on 2 line with address 0x27
|
||||
i2c, err := i2c.NewI2C(0x27, 2)
|
||||
if err != nil { log.Fatal(err) }
|
||||
// Free I2C connection on exit
|
||||
defer i2c.Close()
|
||||
....
|
||||
// Here goes code specific for sending and reading data
|
||||
// to and from device connected via I2C bus, like:
|
||||
_, err := i2c.Write([]byte{0x1, 0xF3})
|
||||
if err != nil { log.Fatal(err) }
|
||||
....
|
||||
}
|
||||
```
|
||||
|
||||
Tutorial
|
||||
--------
|
||||
|
||||
My [repositories](https://github.com/d2r2?tab=repositories) contain quite a lot projects, which use i2c library as a starting point to interact with various peripheral devices and sensors for use on embedded Linux devices. All these libraries start with a standard call to open I2C-connection to specific bus line and address, than pass i2c instance to device.
|
||||
|
||||
In its turn, go-i2c use [go-logger](https://github.com/d2r2/go-logger) library to output debug and other notification's lines which produce all necessary levels of logging. You can manage what level of verbosity you would like to see, by adding call:
|
||||
```go
|
||||
// Uncomment/comment next line to suppress/increase verbosity of output
|
||||
logger.ChangePackageLogLevel("i2c", logger.InfoLevel)
|
||||
```
|
||||
Once you put this call, it will decrease verbosity from default "Debug" up to next "Info" level, reducing the number of low-level console outputs that occur during interaction with the I2C bus. Please, find examples in corresponding I2C-driven sensors among my projects.
|
||||
|
||||
You will find here the list of all devices and sensors supported by me, that reference this library:
|
||||
|
||||
- [Liquid-crystal display driven by Hitachi HD44780 IC](https://github.com/d2r2/go-hd44780).
|
||||
- [BMP180/BMP280/BME280 temperature and pressure sensors](https://github.com/d2r2/go-bsbmp).
|
||||
- [DHT12/AM2320 humidity and temperature sensors](https://github.com/d2r2/go-aosong).
|
||||
- [Si7021 relative humidity and temperature sensor](https://github.com/d2r2/go-si7021).
|
||||
- [SHT3x humidity and temperature sensor](https://github.com/d2r2/go-sht3x).
|
||||
- [VL53L0X time-of-flight ranging sensor](https://github.com/d2r2/go-vl53l0x).
|
||||
- [BH1750 ambient light sensor](https://github.com/d2r2/go-bh1750).
|
||||
- [MPL3115A2 pressure and temperature sensor](https://github.com/d2r2/go-mpl3115a2).
|
||||
|
||||
|
||||
Getting help
|
||||
------------
|
||||
|
||||
GoDoc [documentation](http://godoc.org/github.com/d2r2/go-i2c)
|
||||
|
||||
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 --
|
||||
```
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Go-i2c is licensed under MIT License.
|
|
@ -0,0 +1,12 @@
|
|||
// +build linux,cgo
|
||||
|
||||
package i2c
|
||||
|
||||
// #include <linux/i2c-dev.h>
|
||||
import "C"
|
||||
|
||||
// Get I2C_SLAVE constant value from
|
||||
// Linux OS I2C declaration file.
|
||||
const (
|
||||
I2C_SLAVE = C.I2C_SLAVE
|
||||
)
|
|
@ -0,0 +1,241 @@
|
|||
// Package i2c provides low level control over the Linux i2c bus.
|
||||
//
|
||||
// Before usage you should load the i2c-dev kernel module
|
||||
//
|
||||
// sudo modprobe i2c-dev
|
||||
//
|
||||
// Each i2c bus can address 127 independent i2c devices, and most
|
||||
// Linux systems contain several buses.
|
||||
|
||||
package i2c
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// I2C represents a connection to I2C-device.
|
||||
type I2C struct {
|
||||
addr uint8
|
||||
bus int
|
||||
rc *os.File
|
||||
}
|
||||
|
||||
// NewI2C opens a connection for I2C-device.
|
||||
// SMBus (System Management Bus) protocol over I2C
|
||||
// supported as well: you should preliminary specify
|
||||
// register address to read from, either write register
|
||||
// together with the data in case of write operations.
|
||||
func NewI2C(addr uint8, bus int) (*I2C, error) {
|
||||
f, err := os.OpenFile(fmt.Sprintf("/dev/i2c-%d", bus), os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ioctl(f.Fd(), I2C_SLAVE, uintptr(addr)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v := &I2C{rc: f, bus: bus, addr: addr}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// GetBus return bus line, where I2C-device is allocated.
|
||||
func (v *I2C) GetBus() int {
|
||||
return v.bus
|
||||
}
|
||||
|
||||
// GetAddr return device occupied address in the bus.
|
||||
func (v *I2C) GetAddr() uint8 {
|
||||
return v.addr
|
||||
}
|
||||
|
||||
func (v *I2C) write(buf []byte) (int, error) {
|
||||
return v.rc.Write(buf)
|
||||
}
|
||||
|
||||
// WriteBytes send bytes to the remote I2C-device. The interpretation of
|
||||
// the message is implementation-dependent.
|
||||
func (v *I2C) WriteBytes(buf []byte) (int, error) {
|
||||
lg.Debugf("Write %d hex bytes: [%+v]", len(buf), hex.EncodeToString(buf))
|
||||
return v.write(buf)
|
||||
}
|
||||
|
||||
func (v *I2C) read(buf []byte) (int, error) {
|
||||
return v.rc.Read(buf)
|
||||
}
|
||||
|
||||
// ReadBytes read bytes from I2C-device.
|
||||
// Number of bytes read correspond to buf parameter length.
|
||||
func (v *I2C) ReadBytes(buf []byte) (int, error) {
|
||||
n, err := v.read(buf)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
lg.Debugf("Read %d hex bytes: [%+v]", len(buf), hex.EncodeToString(buf))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// Close I2C-connection.
|
||||
func (v *I2C) Close() error {
|
||||
return v.rc.Close()
|
||||
}
|
||||
|
||||
// ReadRegBytes read count of n byte's sequence from I2C-device
|
||||
// starting from reg address.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegBytes(reg byte, n int) ([]byte, int, error) {
|
||||
lg.Debugf("Read %d bytes starting from reg 0x%0X...", n, reg)
|
||||
_, err := v.WriteBytes([]byte{reg})
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
buf := make([]byte, n)
|
||||
c, err := v.ReadBytes(buf)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return buf, c, nil
|
||||
|
||||
}
|
||||
|
||||
// ReadRegU8 reads byte from I2C-device register specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegU8(reg byte) (byte, error) {
|
||||
_, err := v.WriteBytes([]byte{reg})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
buf := make([]byte, 1)
|
||||
_, err = v.ReadBytes(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lg.Debugf("Read U8 %d from reg 0x%0X", buf[0], reg)
|
||||
return buf[0], nil
|
||||
}
|
||||
|
||||
// WriteRegU8 writes byte to I2C-device register specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) WriteRegU8(reg byte, value byte) error {
|
||||
buf := []byte{reg, value}
|
||||
_, err := v.WriteBytes(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lg.Debugf("Write U8 %d to reg 0x%0X", value, reg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadRegU16BE reads unsigned big endian word (16 bits)
|
||||
// from I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegU16BE(reg byte) (uint16, error) {
|
||||
_, err := v.WriteBytes([]byte{reg})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
buf := make([]byte, 2)
|
||||
_, err = v.ReadBytes(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w := uint16(buf[0])<<8 + uint16(buf[1])
|
||||
lg.Debugf("Read U16 %d from reg 0x%0X", w, reg)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// ReadRegU16LE reads unsigned little endian word (16 bits)
|
||||
// from I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegU16LE(reg byte) (uint16, error) {
|
||||
w, err := v.ReadRegU16BE(reg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// exchange bytes
|
||||
w = (w&0xFF)<<8 + w>>8
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// ReadRegS16BE reads signed big endian word (16 bits)
|
||||
// from I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegS16BE(reg byte) (int16, error) {
|
||||
_, err := v.WriteBytes([]byte{reg})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
buf := make([]byte, 2)
|
||||
_, err = v.ReadBytes(buf)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
w := int16(buf[0])<<8 + int16(buf[1])
|
||||
lg.Debugf("Read S16 %d from reg 0x%0X", w, reg)
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// ReadRegS16LE reads signed little endian word (16 bits)
|
||||
// from I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) ReadRegS16LE(reg byte) (int16, error) {
|
||||
w, err := v.ReadRegS16BE(reg)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// exchange bytes
|
||||
w = (w&0xFF)<<8 + w>>8
|
||||
return w, nil
|
||||
|
||||
}
|
||||
|
||||
// WriteRegU16BE writes unsigned big endian word (16 bits)
|
||||
// value to I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) WriteRegU16BE(reg byte, value uint16) error {
|
||||
buf := []byte{reg, byte((value & 0xFF00) >> 8), byte(value & 0xFF)}
|
||||
_, err := v.WriteBytes(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lg.Debugf("Write U16 %d to reg 0x%0X", value, reg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegU16LE writes unsigned little endian word (16 bits)
|
||||
// value to I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) WriteRegU16LE(reg byte, value uint16) error {
|
||||
w := (value*0xFF00)>>8 + value<<8
|
||||
return v.WriteRegU16BE(reg, w)
|
||||
}
|
||||
|
||||
// WriteRegS16BE writes signed big endian word (16 bits)
|
||||
// value to I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) WriteRegS16BE(reg byte, value int16) error {
|
||||
buf := []byte{reg, byte((uint16(value) & 0xFF00) >> 8), byte(value & 0xFF)}
|
||||
_, err := v.WriteBytes(buf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
lg.Debugf("Write S16 %d to reg 0x%0X", value, reg)
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteRegS16LE writes signed little endian word (16 bits)
|
||||
// value to I2C-device starting from address specified in reg.
|
||||
// SMBus (System Management Bus) protocol over I2C.
|
||||
func (v *I2C) WriteRegS16LE(reg byte, value int16) error {
|
||||
w := int16((uint16(value)*0xFF00)>>8) + value<<8
|
||||
return v.WriteRegS16BE(reg, w)
|
||||
}
|
||||
|
||||
func ioctl(fd, cmd, arg uintptr) error {
|
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, cmd, arg, 0, 0, 0)
|
||||
if err != 0 {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package i2c
|
||||
|
||||
import logger "github.com/d2r2/go-logger"
|
||||
|
||||
// You can manage verbosity of log output
|
||||
// in the package by changing last parameter value
|
||||
// (comment/uncomment corresponding lines).
|
||||
var lg = logger.NewPackageLogger("i2c",
|
||||
logger.DebugLevel,
|
||||
// logger.InfoLevel,
|
||||
)
|
|
@ -0,0 +1,11 @@
|
|||
// +build !cgo
|
||||
|
||||
package i2c
|
||||
|
||||
// Use hard-coded value for system I2C_SLAVE
|
||||
// constant, if OS not Linux or CGO disabled.
|
||||
// This is not a good approach, but
|
||||
// can be used as a last resort.
|
||||
const (
|
||||
I2C_SLAVE = 0x0703
|
||||
)
|
|
@ -0,0 +1,2 @@
|
|||
Golang logger functionality with logging separation by package to improve debug process
|
||||
|
|
@ -0,0 +1,161 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
sync.RWMutex
|
||||
Path string
|
||||
File *os.File
|
||||
}
|
||||
|
||||
func (v *File) Flush() error {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if v.File != nil {
|
||||
err := v.File.Sync()
|
||||
v.File = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *File) Close() error {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if v.File != nil {
|
||||
err := v.File.Close()
|
||||
v.File = nil
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *File) getRotatedFileList() ([]logFile, error) {
|
||||
var list []logFile
|
||||
err := filepath.Walk(path.Dir(v.Path), func(p string,
|
||||
info os.FileInfo, err error) error {
|
||||
pattern := "*" + path.Base(v.Path) + "*"
|
||||
if ok, err := path.Match(pattern, path.Base(p)); ok && err == nil {
|
||||
i := extractIndex(info)
|
||||
list = append(list, logFile{FileInfo: info, Index: i})
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &sortLogFiles{Items: list}
|
||||
sort.Sort(s)
|
||||
return s.Items, nil
|
||||
}
|
||||
|
||||
func (v *File) doRotate(items []logFile, rotateMaxCount int) error {
|
||||
if len(items) > 0 {
|
||||
// delete last files
|
||||
deleteCount := len(items) - rotateMaxCount + 1
|
||||
if deleteCount > 0 {
|
||||
for i := 0; i < deleteCount; i++ {
|
||||
err := os.Remove(items[i].FileInfo.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
items = items[deleteCount:]
|
||||
}
|
||||
// change names of rest files
|
||||
baseFilePath := items[len(items)-1].FileInfo.Name()
|
||||
movs := make([]int, len(items))
|
||||
// 1st round to change names
|
||||
for i, item := range items {
|
||||
movs[i] = i + 100000
|
||||
err := os.Rename(item.FileInfo.Name(),
|
||||
fmt.Sprintf("%s.%d", baseFilePath, movs[i]))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// 2nd round to change names
|
||||
for i, item := range movs {
|
||||
err := os.Rename(fmt.Sprintf("%s.%d", baseFilePath, item),
|
||||
fmt.Sprintf("%s.%d", baseFilePath, len(items)-i))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *File) rotateFiles(rotateMaxSize int64, rotateMaxCount int) error {
|
||||
fs, err := v.File.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.Size() > rotateMaxSize {
|
||||
if v.File != nil {
|
||||
err := v.File.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.File = nil
|
||||
}
|
||||
list, err := v.getRotatedFileList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = v.doRotate(list, rotateMaxCount); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *File) getFile() (*os.File, error) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if v.File == nil {
|
||||
file, err := os.OpenFile(v.Path, os.O_RDWR|os.O_APPEND, 0660)
|
||||
if err != nil {
|
||||
file, err = os.Create(v.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
v.File = file
|
||||
}
|
||||
return v.File, nil
|
||||
}
|
||||
|
||||
func (v *File) writeToFile(msg string, rotateMaxSize int64, rotateMaxCount int) error {
|
||||
file, err := v.getFile()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString(msg)
|
||||
buf.WriteString(fmt.Sprintln())
|
||||
if _, err := io.Copy(file, &buf); err != nil {
|
||||
return err
|
||||
}
|
||||
// if err = file.Sync(); err != nil {
|
||||
// return err
|
||||
// }
|
||||
if err := v.rotateFiles(rotateMaxSize, rotateMaxCount); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package logger
|
||||
|
||||
import "os"
|
||||
|
||||
type LevelLength int
|
||||
|
||||
const (
|
||||
LevelShort LevelLength = iota
|
||||
LevelLong
|
||||
)
|
||||
|
||||
type FormatOptions struct {
|
||||
TimeFormat string
|
||||
PackageLength int
|
||||
LevelLength LevelLength
|
||||
}
|
||||
|
||||
func FormatMessage(options FormatOptions, level LogLevel, packageName, msg string, colored bool) string {
|
||||
appName := os.Args[0]
|
||||
out := metaFmtStr(colored, level, options, appName,
|
||||
packageName, msg, "%[1]s [%[3]s] %[4]s %[5]s")
|
||||
return out
|
||||
}
|
||||
|
||||
func (options FormatOptions) GetLevelStr(level LogLevel) string {
|
||||
if options.LevelLength == LevelLong {
|
||||
return level.LongStr()
|
||||
} else {
|
||||
return level.ShortStr()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,309 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type LogLevel int
|
||||
|
||||
const (
|
||||
FatalLevel LogLevel = iota
|
||||
PanicLevel
|
||||
ErrorLevel
|
||||
WarnLevel
|
||||
NotifyLevel
|
||||
InfoLevel
|
||||
DebugLevel
|
||||
)
|
||||
|
||||
func (v LogLevel) String() string {
|
||||
switch v {
|
||||
case FatalLevel:
|
||||
return "Fatal"
|
||||
case PanicLevel:
|
||||
return "Panic"
|
||||
case ErrorLevel:
|
||||
return "Error"
|
||||
case WarnLevel:
|
||||
return "Warning"
|
||||
case NotifyLevel:
|
||||
return "Notice"
|
||||
case InfoLevel:
|
||||
return "Information"
|
||||
case DebugLevel:
|
||||
return "Debug"
|
||||
default:
|
||||
return "<undef>"
|
||||
}
|
||||
}
|
||||
|
||||
func (v LogLevel) LongStr() string {
|
||||
return v.String()
|
||||
}
|
||||
|
||||
func (v LogLevel) ShortStr() string {
|
||||
switch v {
|
||||
case FatalLevel:
|
||||
return "Fatal"
|
||||
case PanicLevel:
|
||||
return "Panic"
|
||||
case ErrorLevel:
|
||||
return "Error"
|
||||
case WarnLevel:
|
||||
return "Warn"
|
||||
case NotifyLevel:
|
||||
return "Notice"
|
||||
case InfoLevel:
|
||||
return "Info"
|
||||
case DebugLevel:
|
||||
return "Debug"
|
||||
default:
|
||||
return "<undef>"
|
||||
}
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
log *log.Logger
|
||||
colored bool
|
||||
level LogLevel
|
||||
}
|
||||
|
||||
func NewLog(log *log.Logger, colored bool, level LogLevel) *Log {
|
||||
v := &Log{log: log, colored: colored, level: level}
|
||||
return v
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
sync.RWMutex
|
||||
logs []*Log
|
||||
packages []*Package
|
||||
options FormatOptions
|
||||
logFile *File
|
||||
rotateMaxSize int64
|
||||
rotateMaxCount int
|
||||
enableSyslog bool
|
||||
}
|
||||
|
||||
func NewLogger() *Logger {
|
||||
stdout := NewLog(log.New(os.Stdout, "", 0), true, DebugLevel)
|
||||
logs := []*Log{stdout}
|
||||
options := FormatOptions{TimeFormat: "2006-01-02T15:04:05.000", LevelLength: LevelShort, PackageLength: 8}
|
||||
l := &Logger{
|
||||
logs: logs,
|
||||
options: options,
|
||||
rotateMaxSize: 1024 * 1024 * 512,
|
||||
rotateMaxCount: 3,
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (v *Logger) Close() error {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
|
||||
for _, pack := range v.packages {
|
||||
pack.Close()
|
||||
}
|
||||
v.packages = nil
|
||||
|
||||
if v.logFile != nil {
|
||||
v.logFile.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Logger) SetRotateParams(rotateMaxSize int64, rotateMaxCount int) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.rotateMaxSize = rotateMaxSize
|
||||
v.rotateMaxCount = rotateMaxCount
|
||||
}
|
||||
|
||||
func (v *Logger) GetRotateMaxSize() int64 {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
return v.rotateMaxSize
|
||||
}
|
||||
|
||||
func (v *Logger) GetRotateMaxCount() int {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
return v.rotateMaxCount
|
||||
}
|
||||
|
||||
/*
|
||||
func (v *Logger) SetApplicationName(appName string) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.appName = appName
|
||||
}
|
||||
|
||||
func (v *Logger) GetApplicationName() string {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
return v.appName
|
||||
}
|
||||
*/
|
||||
|
||||
func (v *Logger) EnableSyslog(enable bool) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.enableSyslog = enable
|
||||
}
|
||||
|
||||
func (v *Logger) GetSyslogEnabled() bool {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
return v.enableSyslog
|
||||
}
|
||||
|
||||
func (v *Logger) SetFormatOptions(options FormatOptions) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.options = options
|
||||
}
|
||||
|
||||
func (v *Logger) GetFormatOptions() FormatOptions {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
return v.options
|
||||
}
|
||||
|
||||
func (v *Logger) SetLogFileName(logFilePath string) error {
|
||||
if path.Ext(logFilePath) == "" {
|
||||
logFilePath += ".log"
|
||||
}
|
||||
fp, err := filepath.Abs(logFilePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
lf := &File{Path: fp}
|
||||
v.logFile = lf
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Logger) GetLogFileInfo() *File {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
return v.logFile
|
||||
}
|
||||
|
||||
func (v *Logger) NewPackageLogger(packageName string, level LogLevel) PackageLog {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
p := &Package{parent: v, packageName: packageName, level: level}
|
||||
v.packages = append(v.packages, p)
|
||||
return p
|
||||
}
|
||||
|
||||
func (v *Logger) ChangePackageLogLevel(packageName string, level LogLevel) error {
|
||||
var p *Package
|
||||
for _, item := range v.packages {
|
||||
if item.packageName == packageName {
|
||||
p = item
|
||||
break
|
||||
}
|
||||
}
|
||||
if p != nil {
|
||||
p.SetLogLevel(level)
|
||||
} else {
|
||||
err := fmt.Errorf("Package log %q is not found", packageName)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Logger) AddCustomLog(writer io.Writer, colored bool, level LogLevel) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
log := NewLog(log.New(writer, "", 0), colored, level)
|
||||
v.logs = append(v.logs, log)
|
||||
}
|
||||
|
||||
func (v *Logger) getLogs() []*Log {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
lst := []*Log{}
|
||||
for _, item := range v.logs {
|
||||
lst = append(lst, item)
|
||||
}
|
||||
return lst
|
||||
}
|
||||
|
||||
var (
|
||||
globalLock sync.RWMutex
|
||||
lgr *Logger
|
||||
)
|
||||
|
||||
func SetFormatOptions(format FormatOptions) {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
lgr.SetFormatOptions(format)
|
||||
}
|
||||
|
||||
func SetRotateParams(rotateMaxSize int64, rotateMaxCount int) {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
lgr.SetRotateParams(rotateMaxSize, rotateMaxCount)
|
||||
}
|
||||
|
||||
func NewPackageLogger(module string, level LogLevel) PackageLog {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
return lgr.NewPackageLogger(module, level)
|
||||
}
|
||||
|
||||
func ChangePackageLogLevel(packageName string, level LogLevel) error {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
return lgr.ChangePackageLogLevel(packageName, level)
|
||||
}
|
||||
|
||||
func SetLogFileName(logFilePath string) error {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
return lgr.SetLogFileName(logFilePath)
|
||||
}
|
||||
|
||||
/*
|
||||
func SetApplicationName(appName string) {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
lgr.SetApplicationName(appName)
|
||||
}
|
||||
*/
|
||||
|
||||
func EnableSyslog(enable bool) {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
lgr.EnableSyslog(enable)
|
||||
}
|
||||
|
||||
func AddCustomLog(writer io.Writer, colored bool, level LogLevel) {
|
||||
globalLock.RLock()
|
||||
defer globalLock.RUnlock()
|
||||
lgr.AddCustomLog(writer, colored, level)
|
||||
}
|
||||
|
||||
func FinalizeLogger() error {
|
||||
var err error
|
||||
if lgr != nil {
|
||||
err = lgr.Close()
|
||||
}
|
||||
globalLock.Lock()
|
||||
defer globalLock.Unlock()
|
||||
lgr = nil
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
lgr = NewLogger()
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/syslog"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
type PackageLog interface {
|
||||
Printf(level LogLevel, format string, args ...interface{})
|
||||
Print(level LogLevel, args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Debug(args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Info(args ...interface{})
|
||||
Notifyf(format string, args ...interface{})
|
||||
Notify(args ...interface{})
|
||||
Warningf(format string, args ...interface{})
|
||||
Warnf(format string, args ...interface{})
|
||||
Warning(args ...interface{})
|
||||
Warn(args ...interface{})
|
||||
Errorf(format string, args ...interface{})
|
||||
Error(args ...interface{})
|
||||
Panicf(format string, args ...interface{})
|
||||
Panic(args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
Fatal(args ...interface{})
|
||||
}
|
||||
|
||||
type Package struct {
|
||||
sync.RWMutex
|
||||
parent *Logger
|
||||
packageName string
|
||||
level LogLevel
|
||||
syslog *syslog.Writer
|
||||
}
|
||||
|
||||
// Static cast to verify that object implement interface.
|
||||
var _ PackageLog = &Package{}
|
||||
|
||||
func (v *Package) Close() error {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if v.syslog != nil {
|
||||
err := v.syslog.Close()
|
||||
v.syslog = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Package) SetLogLevel(level LogLevel) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
v.level = level
|
||||
}
|
||||
|
||||
func (v *Package) GetLogLevel() LogLevel {
|
||||
v.RLock()
|
||||
defer v.RUnlock()
|
||||
return v.level
|
||||
}
|
||||
|
||||
func (v *Package) getSyslog(level LogLevel, options FormatOptions,
|
||||
appName string) (*syslog.Writer, error) {
|
||||
v.Lock()
|
||||
defer v.Unlock()
|
||||
if v.syslog == nil {
|
||||
tag := metaFmtStr(false, level, options, appName,
|
||||
v.packageName, "", "%[2]s-%[3]s")
|
||||
sl, err := syslog.New(syslog.LOG_DEBUG, tag)
|
||||
if err != nil {
|
||||
err = spew.Errorf("Failed to connect to syslog: %v\n", err)
|
||||
return nil, err
|
||||
}
|
||||
v.syslog = sl
|
||||
}
|
||||
return v.syslog, nil
|
||||
}
|
||||
|
||||
func (v *Package) writeToSyslog(options FormatOptions,
|
||||
level LogLevel, appName string, msg string) error {
|
||||
|
||||
sl, err := v.getSyslog(level, options, appName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
return sl.Debug(msg)
|
||||
case InfoLevel:
|
||||
return sl.Info(msg)
|
||||
case WarnLevel:
|
||||
return sl.Warning(msg)
|
||||
case ErrorLevel:
|
||||
return sl.Err(msg)
|
||||
case PanicLevel:
|
||||
return sl.Crit(msg)
|
||||
case FatalLevel:
|
||||
return sl.Emerg(msg)
|
||||
default:
|
||||
return sl.Debug(msg)
|
||||
}
|
||||
}
|
||||
|
||||
type printLog func(log *Log, msg interface{})
|
||||
type getMessage func(colored bool) interface{}
|
||||
|
||||
func printLogs(logs []*Log, level LogLevel, prnt printLog, getMsg getMessage) {
|
||||
// Console and custom logs output
|
||||
for _, log := range logs {
|
||||
if log.level >= level {
|
||||
prnt(log, getMsg(log.colored))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Package) print(level LogLevel, msg string) {
|
||||
lvl := v.GetLogLevel()
|
||||
if lvl >= level {
|
||||
appName := getApplicationName()
|
||||
logs := v.parent.getLogs()
|
||||
options := v.parent.GetFormatOptions()
|
||||
out1 := FormatMessage(options, level, v.packageName, msg, false)
|
||||
// File output
|
||||
if lf := v.parent.GetLogFileInfo(); lf != nil {
|
||||
rotateMaxSize := v.parent.GetRotateMaxSize()
|
||||
rotateMaxCount := v.parent.GetRotateMaxCount()
|
||||
if err := lf.writeToFile(out1, rotateMaxSize, rotateMaxCount); err != nil {
|
||||
err = spew.Errorf("Failed to report syslog message %q: %v\n", out1, err)
|
||||
printLogs(logs, FatalLevel,
|
||||
func(log *Log, msg interface{}) {
|
||||
log.log.Fatal(msg)
|
||||
},
|
||||
func(colored bool) interface{} {
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
// Syslog output
|
||||
if v.parent.GetSyslogEnabled() {
|
||||
if err := v.writeToSyslog(options, level, appName, msg); err != nil {
|
||||
err = spew.Errorf("Failed to report syslog message %q: %v\n", msg, err)
|
||||
printLogs(logs, FatalLevel,
|
||||
func(log *Log, msg interface{}) {
|
||||
log.log.Fatal(msg)
|
||||
},
|
||||
func(colored bool) interface{} {
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
// Console and custom logs output
|
||||
outColored1 := FormatMessage(options, level, v.packageName, msg, true)
|
||||
printLogs(logs, level,
|
||||
func(log *Log, msg interface{}) {
|
||||
log.log.Print(msg)
|
||||
},
|
||||
func(colored bool) interface{} {
|
||||
if colored {
|
||||
return outColored1 + fmt.Sprintln()
|
||||
} else {
|
||||
return out1 + fmt.Sprintln()
|
||||
}
|
||||
})
|
||||
// Check critical events
|
||||
if level == PanicLevel {
|
||||
panic(out1)
|
||||
} else if level == FatalLevel {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Package) Printf(level LogLevel, format string, args ...interface{}) {
|
||||
lvl := v.GetLogLevel()
|
||||
if lvl >= level {
|
||||
msg := spew.Sprintf(format, args...)
|
||||
v.print(level, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Package) Print(level LogLevel, args ...interface{}) {
|
||||
lvl := v.GetLogLevel()
|
||||
if lvl >= level {
|
||||
msg := fmt.Sprint(args...)
|
||||
v.print(level, msg)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Package) Debugf(format string, args ...interface{}) {
|
||||
v.Printf(DebugLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Debug(args ...interface{}) {
|
||||
v.Print(DebugLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Infof(format string, args ...interface{}) {
|
||||
v.Printf(InfoLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Info(args ...interface{}) {
|
||||
v.Print(InfoLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Notifyf(format string, args ...interface{}) {
|
||||
v.Printf(NotifyLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Notify(args ...interface{}) {
|
||||
v.Print(NotifyLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Warningf(format string, args ...interface{}) {
|
||||
v.Printf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Warnf(format string, args ...interface{}) {
|
||||
v.Printf(WarnLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Warning(args ...interface{}) {
|
||||
v.Print(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Warn(args ...interface{}) {
|
||||
v.Print(WarnLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Errorf(format string, args ...interface{}) {
|
||||
v.Printf(ErrorLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Error(args ...interface{}) {
|
||||
v.Print(ErrorLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Panicf(format string, args ...interface{}) {
|
||||
v.Printf(PanicLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Panic(args ...interface{}) {
|
||||
v.Print(PanicLevel, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Fatalf(format string, args ...interface{}) {
|
||||
v.Printf(FatalLevel, format, args...)
|
||||
}
|
||||
|
||||
func (v *Package) Fatal(args ...interface{}) {
|
||||
v.Print(FatalLevel, args...)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type logFile struct {
|
||||
FileInfo os.FileInfo
|
||||
Index int
|
||||
}
|
||||
|
||||
type sortLogFiles struct {
|
||||
Items []logFile
|
||||
}
|
||||
|
||||
func (sf *sortLogFiles) Len() int {
|
||||
return len(sf.Items)
|
||||
}
|
||||
|
||||
func (sf *sortLogFiles) Less(i, j int) bool {
|
||||
return sf.Items[j].Index < sf.Items[i].Index
|
||||
}
|
||||
|
||||
func (sf *sortLogFiles) Swap(i, j int) {
|
||||
item := sf.Items[i]
|
||||
sf.Items[i] = sf.Items[j]
|
||||
sf.Items[j] = item
|
||||
}
|
||||
|
||||
func findStringSubmatchIndexes(r *regexp.Regexp, s string) map[string][2]int {
|
||||
captures := make(map[string][2]int)
|
||||
ind := r.FindStringSubmatchIndex(s)
|
||||
names := r.SubexpNames()
|
||||
for i, name := range names {
|
||||
if name != "" && i < len(ind)/2 {
|
||||
if ind[i*2] != -1 && ind[i*2+1] != -1 {
|
||||
captures[name] = [2]int{ind[i*2], ind[i*2+1]}
|
||||
}
|
||||
}
|
||||
}
|
||||
return captures
|
||||
}
|
||||
|
||||
func extractIndex(item os.FileInfo) int {
|
||||
r := regexp.MustCompile(`.+\.log(\.(?P<index>\d+))?`)
|
||||
fileName := path.Base(item.Name())
|
||||
m := findStringSubmatchIndexes(r, fileName)
|
||||
if v, ok := m["index"]; ok {
|
||||
i, _ := strconv.Atoi(fileName[v[0]:v[1]])
|
||||
return i
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
nocolor = 0
|
||||
red = 31
|
||||
green = 32
|
||||
yellow = 33
|
||||
blue = 34
|
||||
gray = 37
|
||||
)
|
||||
|
||||
type IndentKind int
|
||||
|
||||
const (
|
||||
LeftIndent = iota
|
||||
CenterIndent
|
||||
RightIndent
|
||||
)
|
||||
|
||||
func cutOrIndentText(text string, length int, indent IndentKind) string {
|
||||
if length < 0 {
|
||||
return text
|
||||
} else if len(text) > length {
|
||||
text = text[:length]
|
||||
} else {
|
||||
switch indent {
|
||||
case LeftIndent:
|
||||
text = text + strings.Repeat(" ", length-len(text))
|
||||
case RightIndent:
|
||||
text = strings.Repeat(" ", length-len(text)) + text
|
||||
case CenterIndent:
|
||||
text = strings.Repeat(" ", (length-len(text))/2) + text +
|
||||
strings.Repeat(" ", length-len(text)-(length-len(text))/2)
|
||||
|
||||
}
|
||||
}
|
||||
return text
|
||||
}
|
||||
|
||||
func metaFmtStr(colored bool, level LogLevel, options FormatOptions, appName string,
|
||||
packageName string, message string, format string) string {
|
||||
var colorPfx, colorSfx string
|
||||
if colored {
|
||||
var levelColor int
|
||||
switch level {
|
||||
case DebugLevel:
|
||||
levelColor = gray
|
||||
case InfoLevel:
|
||||
levelColor = blue
|
||||
case NotifyLevel, WarnLevel:
|
||||
levelColor = yellow
|
||||
case ErrorLevel, PanicLevel, FatalLevel:
|
||||
levelColor = red
|
||||
default:
|
||||
levelColor = nocolor
|
||||
}
|
||||
colorPfx = "\x1b[" + strconv.Itoa(levelColor) + "m"
|
||||
colorSfx = "\x1b[0m"
|
||||
}
|
||||
arg1 := time.Now().Format(options.TimeFormat)
|
||||
arg2 := appName
|
||||
arg3 := cutOrIndentText(packageName, options.PackageLength, RightIndent)
|
||||
lvlStr := options.GetLevelStr(level)
|
||||
lvlLen := len([]rune(lvlStr))
|
||||
arg4 := colorPfx + cutOrIndentText(strings.ToUpper(lvlStr), lvlLen, LeftIndent) + colorSfx
|
||||
arg5 := message
|
||||
out := fmt.Sprintf(format, arg1, arg2, arg3, arg4, arg5)
|
||||
return out
|
||||
}
|
||||
|
||||
func getApplicationName() string {
|
||||
appName := os.Args[0]
|
||||
return appName
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
ISC License
|
||||
|
||||
Copyright (c) 2012-2016 Dave Collins <dave@davec.name>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
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.
|
|
@ -0,0 +1,145 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is not running on Google App Engine, compiled by GopherJS, and
|
||||
// "-tags safe" is not added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// Go versions prior to 1.4 are disabled because they use a different layout
|
||||
// for interfaces which make the implementation of unsafeReflectValue more complex.
|
||||
// +build !js,!appengine,!safe,!disableunsafe,go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = false
|
||||
|
||||
// ptrSize is the size of a pointer on the current arch.
|
||||
ptrSize = unsafe.Sizeof((*byte)(nil))
|
||||
)
|
||||
|
||||
type flag uintptr
|
||||
|
||||
var (
|
||||
// flagRO indicates whether the value field of a reflect.Value
|
||||
// is read-only.
|
||||
flagRO flag
|
||||
|
||||
// flagAddr indicates whether the address of the reflect.Value's
|
||||
// value may be taken.
|
||||
flagAddr flag
|
||||
)
|
||||
|
||||
// flagKindMask holds the bits that make up the kind
|
||||
// part of the flags field. In all the supported versions,
|
||||
// it is in the lower 5 bits.
|
||||
const flagKindMask = flag(0x1f)
|
||||
|
||||
// Different versions of Go have used different
|
||||
// bit layouts for the flags type. This table
|
||||
// records the known combinations.
|
||||
var okFlags = []struct {
|
||||
ro, addr flag
|
||||
}{{
|
||||
// From Go 1.4 to 1.5
|
||||
ro: 1 << 5,
|
||||
addr: 1 << 7,
|
||||
}, {
|
||||
// Up to Go tip.
|
||||
ro: 1<<5 | 1<<6,
|
||||
addr: 1 << 8,
|
||||
}}
|
||||
|
||||
var flagValOffset = func() uintptr {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
return field.Offset
|
||||
}()
|
||||
|
||||
// flagField returns a pointer to the flag field of a reflect.Value.
|
||||
func flagField(v *reflect.Value) *flag {
|
||||
return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset))
|
||||
}
|
||||
|
||||
// unsafeReflectValue converts the passed reflect.Value into a one that bypasses
|
||||
// the typical safety restrictions preventing access to unaddressable and
|
||||
// unexported data. It works by digging the raw pointer to the underlying
|
||||
// value out of the protected value and generating a new unprotected (unsafe)
|
||||
// reflect.Value to it.
|
||||
//
|
||||
// This allows us to check for implementations of the Stringer and error
|
||||
// interfaces to be used for pretty printing ordinarily unaddressable and
|
||||
// inaccessible values such as unexported struct fields.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
if !v.IsValid() || (v.CanInterface() && v.CanAddr()) {
|
||||
return v
|
||||
}
|
||||
flagFieldPtr := flagField(&v)
|
||||
*flagFieldPtr &^= flagRO
|
||||
*flagFieldPtr |= flagAddr
|
||||
return v
|
||||
}
|
||||
|
||||
// Sanity checks against future reflect package changes
|
||||
// to the type or semantics of the Value.flag field.
|
||||
func init() {
|
||||
field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag")
|
||||
if !ok {
|
||||
panic("reflect.Value has no flag field")
|
||||
}
|
||||
if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() {
|
||||
panic("reflect.Value flag field has changed kind")
|
||||
}
|
||||
type t0 int
|
||||
var t struct {
|
||||
A t0
|
||||
// t0 will have flagEmbedRO set.
|
||||
t0
|
||||
// a will have flagStickyRO set
|
||||
a t0
|
||||
}
|
||||
vA := reflect.ValueOf(t).FieldByName("A")
|
||||
va := reflect.ValueOf(t).FieldByName("a")
|
||||
vt0 := reflect.ValueOf(t).FieldByName("t0")
|
||||
|
||||
// Infer flagRO from the difference between the flags
|
||||
// for the (otherwise identical) fields in t.
|
||||
flagPublic := *flagField(&vA)
|
||||
flagWithRO := *flagField(&va) | *flagField(&vt0)
|
||||
flagRO = flagPublic ^ flagWithRO
|
||||
|
||||
// Infer flagAddr from the difference between a value
|
||||
// taken from a pointer and not.
|
||||
vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A")
|
||||
flagNoPtr := *flagField(&vA)
|
||||
flagPtr := *flagField(&vPtrA)
|
||||
flagAddr = flagNoPtr ^ flagPtr
|
||||
|
||||
// Check that the inferred flags tally with one of the known versions.
|
||||
for _, f := range okFlags {
|
||||
if flagRO == f.ro && flagAddr == f.addr {
|
||||
return
|
||||
}
|
||||
}
|
||||
panic("reflect.Value read-only flag has changed semantics")
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright (c) 2015-2016 Dave Collins <dave@davec.name>
|
||||
//
|
||||
// Permission to use, copy, modify, and distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// 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.
|
||||
|
||||
// NOTE: Due to the following build constraints, this file will only be compiled
|
||||
// when the code is running on Google App Engine, compiled by GopherJS, or
|
||||
// "-tags safe" is added to the go build command line. The "disableunsafe"
|
||||
// tag is deprecated and thus should not be used.
|
||||
// +build js appengine safe disableunsafe !go1.4
|
||||
|
||||
package spew
|
||||
|
||||
import "reflect"
|
||||
|
||||
const (
|
||||
// UnsafeDisabled is a build-time constant which specifies whether or
|
||||
// not access to the unsafe package is available.
|
||||
UnsafeDisabled = true
|
||||
)
|
||||
|
||||
// unsafeReflectValue typically converts the passed reflect.Value into a one
|
||||
// that bypasses the typical safety restrictions preventing access to
|
||||
// unaddressable and unexported data. However, doing this relies on access to
|
||||
// the unsafe package. This is a stub version which simply returns the passed
|
||||
// reflect.Value when the unsafe package is not available.
|
||||
func unsafeReflectValue(v reflect.Value) reflect.Value {
|
||||
return v
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// Some constants in the form of bytes to avoid string overhead. This mirrors
|
||||
// the technique used in the fmt package.
|
||||
var (
|
||||
panicBytes = []byte("(PANIC=")
|
||||
plusBytes = []byte("+")
|
||||
iBytes = []byte("i")
|
||||
trueBytes = []byte("true")
|
||||
falseBytes = []byte("false")
|
||||
interfaceBytes = []byte("(interface {})")
|
||||
commaNewlineBytes = []byte(",\n")
|
||||
newlineBytes = []byte("\n")
|
||||
openBraceBytes = []byte("{")
|
||||
openBraceNewlineBytes = []byte("{\n")
|
||||
closeBraceBytes = []byte("}")
|
||||
asteriskBytes = []byte("*")
|
||||
colonBytes = []byte(":")
|
||||
colonSpaceBytes = []byte(": ")
|
||||
openParenBytes = []byte("(")
|
||||
closeParenBytes = []byte(")")
|
||||
spaceBytes = []byte(" ")
|
||||
pointerChainBytes = []byte("->")
|
||||
nilAngleBytes = []byte("<nil>")
|
||||
maxNewlineBytes = []byte("<max depth reached>\n")
|
||||
maxShortBytes = []byte("<max>")
|
||||
circularBytes = []byte("<already shown>")
|
||||
circularShortBytes = []byte("<shown>")
|
||||
invalidAngleBytes = []byte("<invalid>")
|
||||
openBracketBytes = []byte("[")
|
||||
closeBracketBytes = []byte("]")
|
||||
percentBytes = []byte("%")
|
||||
precisionBytes = []byte(".")
|
||||
openAngleBytes = []byte("<")
|
||||
closeAngleBytes = []byte(">")
|
||||
openMapBytes = []byte("map[")
|
||||
closeMapBytes = []byte("]")
|
||||
lenEqualsBytes = []byte("len=")
|
||||
capEqualsBytes = []byte("cap=")
|
||||
)
|
||||
|
||||
// hexDigits is used to map a decimal value to a hex digit.
|
||||
var hexDigits = "0123456789abcdef"
|
||||
|
||||
// catchPanic handles any panics that might occur during the handleMethods
|
||||
// calls.
|
||||
func catchPanic(w io.Writer, v reflect.Value) {
|
||||
if err := recover(); err != nil {
|
||||
w.Write(panicBytes)
|
||||
fmt.Fprintf(w, "%v", err)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// handleMethods attempts to call the Error and String methods on the underlying
|
||||
// type the passed reflect.Value represents and outputes the result to Writer w.
|
||||
//
|
||||
// It handles panics in any called methods by catching and displaying the error
|
||||
// as the formatted value.
|
||||
func handleMethods(cs *ConfigState, w io.Writer, v reflect.Value) (handled bool) {
|
||||
// We need an interface to check if the type implements the error or
|
||||
// Stringer interface. However, the reflect package won't give us an
|
||||
// interface on certain things like unexported struct fields in order
|
||||
// to enforce visibility rules. We use unsafe, when it's available,
|
||||
// to bypass these restrictions since this package does not mutate the
|
||||
// values.
|
||||
if !v.CanInterface() {
|
||||
if UnsafeDisabled {
|
||||
return false
|
||||
}
|
||||
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
|
||||
// Choose whether or not to do error and Stringer interface lookups against
|
||||
// the base type or a pointer to the base type depending on settings.
|
||||
// Technically calling one of these methods with a pointer receiver can
|
||||
// mutate the value, however, types which choose to satisify an error or
|
||||
// Stringer interface with a pointer receiver should not be mutating their
|
||||
// state inside these interface methods.
|
||||
if !cs.DisablePointerMethods && !UnsafeDisabled && !v.CanAddr() {
|
||||
v = unsafeReflectValue(v)
|
||||
}
|
||||
if v.CanAddr() {
|
||||
v = v.Addr()
|
||||
}
|
||||
|
||||
// Is it an error or Stringer?
|
||||
switch iface := v.Interface().(type) {
|
||||
case error:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.Error()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
|
||||
w.Write([]byte(iface.Error()))
|
||||
return true
|
||||
|
||||
case fmt.Stringer:
|
||||
defer catchPanic(w, v)
|
||||
if cs.ContinueOnMethod {
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(iface.String()))
|
||||
w.Write(closeParenBytes)
|
||||
w.Write(spaceBytes)
|
||||
return false
|
||||
}
|
||||
w.Write([]byte(iface.String()))
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// printBool outputs a boolean value as true or false to Writer w.
|
||||
func printBool(w io.Writer, val bool) {
|
||||
if val {
|
||||
w.Write(trueBytes)
|
||||
} else {
|
||||
w.Write(falseBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// printInt outputs a signed integer value to Writer w.
|
||||
func printInt(w io.Writer, val int64, base int) {
|
||||
w.Write([]byte(strconv.FormatInt(val, base)))
|
||||
}
|
||||
|
||||
// printUint outputs an unsigned integer value to Writer w.
|
||||
func printUint(w io.Writer, val uint64, base int) {
|
||||
w.Write([]byte(strconv.FormatUint(val, base)))
|
||||
}
|
||||
|
||||
// printFloat outputs a floating point value using the specified precision,
|
||||
// which is expected to be 32 or 64bit, to Writer w.
|
||||
func printFloat(w io.Writer, val float64, precision int) {
|
||||
w.Write([]byte(strconv.FormatFloat(val, 'g', -1, precision)))
|
||||
}
|
||||
|
||||
// printComplex outputs a complex value using the specified float precision
|
||||
// for the real and imaginary parts to Writer w.
|
||||
func printComplex(w io.Writer, c complex128, floatPrecision int) {
|
||||
r := real(c)
|
||||
w.Write(openParenBytes)
|
||||
w.Write([]byte(strconv.FormatFloat(r, 'g', -1, floatPrecision)))
|
||||
i := imag(c)
|
||||
if i >= 0 {
|
||||
w.Write(plusBytes)
|
||||
}
|
||||
w.Write([]byte(strconv.FormatFloat(i, 'g', -1, floatPrecision)))
|
||||
w.Write(iBytes)
|
||||
w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x'
|
||||
// prefix to Writer w.
|
||||
func printHexPtr(w io.Writer, p uintptr) {
|
||||
// Null pointer.
|
||||
num := uint64(p)
|
||||
if num == 0 {
|
||||
w.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Max uint64 is 16 bytes in hex + 2 bytes for '0x' prefix
|
||||
buf := make([]byte, 18)
|
||||
|
||||
// It's simpler to construct the hex string right to left.
|
||||
base := uint64(16)
|
||||
i := len(buf) - 1
|
||||
for num >= base {
|
||||
buf[i] = hexDigits[num%base]
|
||||
num /= base
|
||||
i--
|
||||
}
|
||||
buf[i] = hexDigits[num]
|
||||
|
||||
// Add '0x' prefix.
|
||||
i--
|
||||
buf[i] = 'x'
|
||||
i--
|
||||
buf[i] = '0'
|
||||
|
||||
// Strip unused leading bytes.
|
||||
buf = buf[i:]
|
||||
w.Write(buf)
|
||||
}
|
||||
|
||||
// valuesSorter implements sort.Interface to allow a slice of reflect.Value
|
||||
// elements to be sorted.
|
||||
type valuesSorter struct {
|
||||
values []reflect.Value
|
||||
strings []string // either nil or same len and values
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// newValuesSorter initializes a valuesSorter instance, which holds a set of
|
||||
// surrogate keys on which the data should be sorted. It uses flags in
|
||||
// ConfigState to decide if and how to populate those surrogate keys.
|
||||
func newValuesSorter(values []reflect.Value, cs *ConfigState) sort.Interface {
|
||||
vs := &valuesSorter{values: values, cs: cs}
|
||||
if canSortSimply(vs.values[0].Kind()) {
|
||||
return vs
|
||||
}
|
||||
if !cs.DisableMethods {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
b := bytes.Buffer{}
|
||||
if !handleMethods(cs, &b, vs.values[i]) {
|
||||
vs.strings = nil
|
||||
break
|
||||
}
|
||||
vs.strings[i] = b.String()
|
||||
}
|
||||
}
|
||||
if vs.strings == nil && cs.SpewKeys {
|
||||
vs.strings = make([]string, len(values))
|
||||
for i := range vs.values {
|
||||
vs.strings[i] = Sprintf("%#v", vs.values[i].Interface())
|
||||
}
|
||||
}
|
||||
return vs
|
||||
}
|
||||
|
||||
// canSortSimply tests whether a reflect.Kind is a primitive that can be sorted
|
||||
// directly, or whether it should be considered for sorting by surrogate keys
|
||||
// (if the ConfigState allows it).
|
||||
func canSortSimply(kind reflect.Kind) bool {
|
||||
// This switch parallels valueSortLess, except for the default case.
|
||||
switch kind {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return true
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return true
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Uintptr:
|
||||
return true
|
||||
case reflect.Array:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Len returns the number of values in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Len() int {
|
||||
return len(s.values)
|
||||
}
|
||||
|
||||
// Swap swaps the values at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s *valuesSorter) Swap(i, j int) {
|
||||
s.values[i], s.values[j] = s.values[j], s.values[i]
|
||||
if s.strings != nil {
|
||||
s.strings[i], s.strings[j] = s.strings[j], s.strings[i]
|
||||
}
|
||||
}
|
||||
|
||||
// valueSortLess returns whether the first value should sort before the second
|
||||
// value. It is used by valueSorter.Less as part of the sort.Interface
|
||||
// implementation.
|
||||
func valueSortLess(a, b reflect.Value) bool {
|
||||
switch a.Kind() {
|
||||
case reflect.Bool:
|
||||
return !a.Bool() && b.Bool()
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
return a.Int() < b.Int()
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return a.Float() < b.Float()
|
||||
case reflect.String:
|
||||
return a.String() < b.String()
|
||||
case reflect.Uintptr:
|
||||
return a.Uint() < b.Uint()
|
||||
case reflect.Array:
|
||||
// Compare the contents of both arrays.
|
||||
l := a.Len()
|
||||
for i := 0; i < l; i++ {
|
||||
av := a.Index(i)
|
||||
bv := b.Index(i)
|
||||
if av.Interface() == bv.Interface() {
|
||||
continue
|
||||
}
|
||||
return valueSortLess(av, bv)
|
||||
}
|
||||
}
|
||||
return a.String() < b.String()
|
||||
}
|
||||
|
||||
// Less returns whether the value at index i should sort before the
|
||||
// value at index j. It is part of the sort.Interface implementation.
|
||||
func (s *valuesSorter) Less(i, j int) bool {
|
||||
if s.strings == nil {
|
||||
return valueSortLess(s.values[i], s.values[j])
|
||||
}
|
||||
return s.strings[i] < s.strings[j]
|
||||
}
|
||||
|
||||
// sortValues is a sort function that handles both native types and any type that
|
||||
// can be converted to error or Stringer. Other inputs are sorted according to
|
||||
// their Value.String() value to ensure display stability.
|
||||
func sortValues(values []reflect.Value, cs *ConfigState) {
|
||||
if len(values) == 0 {
|
||||
return
|
||||
}
|
||||
sort.Sort(newValuesSorter(values, cs))
|
||||
}
|
|
@ -0,0 +1,306 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
)
|
||||
|
||||
// ConfigState houses the configuration options used by spew to format and
|
||||
// display values. There is a global instance, Config, that is used to control
|
||||
// all top-level Formatter and Dump functionality. Each ConfigState instance
|
||||
// provides methods equivalent to the top-level functions.
|
||||
//
|
||||
// The zero value for ConfigState provides no indentation. You would typically
|
||||
// want to set it to a space or a tab.
|
||||
//
|
||||
// Alternatively, you can use NewDefaultConfig to get a ConfigState instance
|
||||
// with default settings. See the documentation of NewDefaultConfig for default
|
||||
// values.
|
||||
type ConfigState struct {
|
||||
// Indent specifies the string to use for each indentation level. The
|
||||
// global config instance that all top-level functions use set this to a
|
||||
// single space by default. If you would like more indentation, you might
|
||||
// set this to a tab with "\t" or perhaps two spaces with " ".
|
||||
Indent string
|
||||
|
||||
// MaxDepth controls the maximum number of levels to descend into nested
|
||||
// data structures. The default, 0, means there is no limit.
|
||||
//
|
||||
// NOTE: Circular data structures are properly detected, so it is not
|
||||
// necessary to set this value unless you specifically want to limit deeply
|
||||
// nested data structures.
|
||||
MaxDepth int
|
||||
|
||||
// DisableMethods specifies whether or not error and Stringer interfaces are
|
||||
// invoked for types that implement them.
|
||||
DisableMethods bool
|
||||
|
||||
// DisablePointerMethods specifies whether or not to check for and invoke
|
||||
// error and Stringer interfaces on types which only accept a pointer
|
||||
// receiver when the current type is not a pointer.
|
||||
//
|
||||
// NOTE: This might be an unsafe action since calling one of these methods
|
||||
// with a pointer receiver could technically mutate the value, however,
|
||||
// in practice, types which choose to satisify an error or Stringer
|
||||
// interface with a pointer receiver should not be mutating their state
|
||||
// inside these interface methods. As a result, this option relies on
|
||||
// access to the unsafe package, so it will not have any effect when
|
||||
// running in environments without access to the unsafe package such as
|
||||
// Google App Engine or with the "safe" build tag specified.
|
||||
DisablePointerMethods bool
|
||||
|
||||
// DisablePointerAddresses specifies whether to disable the printing of
|
||||
// pointer addresses. This is useful when diffing data structures in tests.
|
||||
DisablePointerAddresses bool
|
||||
|
||||
// DisableCapacities specifies whether to disable the printing of capacities
|
||||
// for arrays, slices, maps and channels. This is useful when diffing
|
||||
// data structures in tests.
|
||||
DisableCapacities bool
|
||||
|
||||
// ContinueOnMethod specifies whether or not recursion should continue once
|
||||
// a custom error or Stringer interface is invoked. The default, false,
|
||||
// means it will print the results of invoking the custom error or Stringer
|
||||
// interface and return immediately instead of continuing to recurse into
|
||||
// the internals of the data type.
|
||||
//
|
||||
// NOTE: This flag does not have any effect if method invocation is disabled
|
||||
// via the DisableMethods or DisablePointerMethods options.
|
||||
ContinueOnMethod bool
|
||||
|
||||
// SortKeys specifies map keys should be sorted before being printed. Use
|
||||
// this to have a more deterministic, diffable output. Note that only
|
||||
// native types (bool, int, uint, floats, uintptr and string) and types
|
||||
// that support the error or Stringer interfaces (if methods are
|
||||
// enabled) are supported, with other types sorted according to the
|
||||
// reflect.Value.String() output which guarantees display stability.
|
||||
SortKeys bool
|
||||
|
||||
// SpewKeys specifies that, as a last resort attempt, map keys should
|
||||
// be spewed to strings and sorted by those strings. This is only
|
||||
// considered if SortKeys is true.
|
||||
SpewKeys bool
|
||||
}
|
||||
|
||||
// Config is the active configuration of the top-level functions.
|
||||
// The configuration can be changed by modifying the contents of spew.Config.
|
||||
var Config = ConfigState{Indent: " "}
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the formatted string as a value that satisfies error. See NewFormatter
|
||||
// for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a Formatter interface returned by c.NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a Formatter interface returned by c.NewFormatter. It returns
|
||||
// the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a Formatter interface returned by c.NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(c.NewFormatter(a), c.NewFormatter(b))
|
||||
func (c *ConfigState) Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(c.convertArgs(a)...)
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), and %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
c.Printf, c.Println, or c.Printf.
|
||||
*/
|
||||
func (c *ConfigState) NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(c, v)
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func (c *ConfigState) Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(c, w, a...)
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by modifying the public members
|
||||
of c. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func (c *ConfigState) Dump(a ...interface{}) {
|
||||
fdump(c, os.Stdout, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func (c *ConfigState) Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(c, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a spew Formatter interface using
|
||||
// the ConfigState associated with s.
|
||||
func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = newFormatter(c, arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
||||
|
||||
// NewDefaultConfig returns a ConfigState with the following default settings.
|
||||
//
|
||||
// Indent: " "
|
||||
// MaxDepth: 0
|
||||
// DisableMethods: false
|
||||
// DisablePointerMethods: false
|
||||
// ContinueOnMethod: false
|
||||
// SortKeys: false
|
||||
func NewDefaultConfig() *ConfigState {
|
||||
return &ConfigState{Indent: " "}
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/*
|
||||
Package spew implements a deep pretty printer for Go data structures to aid in
|
||||
debugging.
|
||||
|
||||
A quick overview of the additional features spew provides over the built-in
|
||||
printing facilities for Go data types are as follows:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output (only when using
|
||||
Dump style)
|
||||
|
||||
There are two different approaches spew allows for dumping Go data structures:
|
||||
|
||||
* Dump style which prints with newlines, customizable indentation,
|
||||
and additional debug information such as types and all pointer addresses
|
||||
used to indirect to the final value
|
||||
* A custom Formatter interface that integrates cleanly with the standard fmt
|
||||
package and replaces %v, %+v, %#v, and %#+v to provide inline printing
|
||||
similar to the default %v while providing the additional functionality
|
||||
outlined above and passing unsupported format verbs such as %x and %q
|
||||
along to fmt
|
||||
|
||||
Quick Start
|
||||
|
||||
This section demonstrates how to quickly get started with spew. See the
|
||||
sections below for further details on formatting and configuration options.
|
||||
|
||||
To dump a variable with full newlines, indentation, type, and pointer
|
||||
information use Dump, Fdump, or Sdump:
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
spew.Fdump(someWriter, myVar1, myVar2, ...)
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Alternatively, if you would prefer to use format strings with a compacted inline
|
||||
printing style, use the convenience wrappers Printf, Fprintf, etc with
|
||||
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or
|
||||
%#+v (adds types and pointer addresses):
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
Configuration Options
|
||||
|
||||
Configuration of spew is handled by fields in the ConfigState type. For
|
||||
convenience, all of the top-level functions use a global state available
|
||||
via the spew.Config global.
|
||||
|
||||
It is also possible to create a ConfigState instance that provides methods
|
||||
equivalent to the top-level functions. This allows concurrent configuration
|
||||
options. See the ConfigState documentation for more details.
|
||||
|
||||
The following configuration options are available:
|
||||
* Indent
|
||||
String to use for each indentation level for Dump functions.
|
||||
It is a single space by default. A popular alternative is "\t".
|
||||
|
||||
* MaxDepth
|
||||
Maximum number of levels to descend into nested data structures.
|
||||
There is no limit by default.
|
||||
|
||||
* DisableMethods
|
||||
Disables invocation of error and Stringer interface methods.
|
||||
Method invocation is enabled by default.
|
||||
|
||||
* DisablePointerMethods
|
||||
Disables invocation of error and Stringer interface methods on types
|
||||
which only accept pointer receivers from non-pointer variables.
|
||||
Pointer method invocation is enabled by default.
|
||||
|
||||
* DisablePointerAddresses
|
||||
DisablePointerAddresses specifies whether to disable the printing of
|
||||
pointer addresses. This is useful when diffing data structures in tests.
|
||||
|
||||
* DisableCapacities
|
||||
DisableCapacities specifies whether to disable the printing of
|
||||
capacities for arrays, slices, maps and channels. This is useful when
|
||||
diffing data structures in tests.
|
||||
|
||||
* ContinueOnMethod
|
||||
Enables recursion into types after invoking error and Stringer interface
|
||||
methods. Recursion after method invocation is disabled by default.
|
||||
|
||||
* SortKeys
|
||||
Specifies map keys should be sorted before being printed. Use
|
||||
this to have a more deterministic, diffable output. Note that
|
||||
only native types (bool, int, uint, floats, uintptr and string)
|
||||
and types which implement error or Stringer interfaces are
|
||||
supported with other types sorted according to the
|
||||
reflect.Value.String() output which guarantees display
|
||||
stability. Natural map order is used by default.
|
||||
|
||||
* SpewKeys
|
||||
Specifies that, as a last resort attempt, map keys should be
|
||||
spewed to strings and sorted by those strings. This is only
|
||||
considered if SortKeys is true.
|
||||
|
||||
Dump Usage
|
||||
|
||||
Simply call spew.Dump with a list of variables you want to dump:
|
||||
|
||||
spew.Dump(myVar1, myVar2, ...)
|
||||
|
||||
You may also call spew.Fdump if you would prefer to output to an arbitrary
|
||||
io.Writer. For example, to dump to standard error:
|
||||
|
||||
spew.Fdump(os.Stderr, myVar1, myVar2, ...)
|
||||
|
||||
A third option is to call spew.Sdump to get the formatted output as a string:
|
||||
|
||||
str := spew.Sdump(myVar1, myVar2, ...)
|
||||
|
||||
Sample Dump Output
|
||||
|
||||
See the Dump example for details on the setup of the types and variables being
|
||||
shown here.
|
||||
|
||||
(main.Foo) {
|
||||
unexportedField: (*main.Bar)(0xf84002e210)({
|
||||
flag: (main.Flag) flagTwo,
|
||||
data: (uintptr) <nil>
|
||||
}),
|
||||
ExportedField: (map[interface {}]interface {}) (len=1) {
|
||||
(string) (len=3) "one": (bool) true
|
||||
}
|
||||
}
|
||||
|
||||
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
|
||||
command as shown.
|
||||
([]uint8) (len=32 cap=32) {
|
||||
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
|
||||
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
|
||||
00000020 31 32 |12|
|
||||
}
|
||||
|
||||
Custom Formatter
|
||||
|
||||
Spew provides a custom formatter that implements the fmt.Formatter interface
|
||||
so that it integrates cleanly with standard fmt package printing functions. The
|
||||
formatter is useful for inline printing of smaller data types similar to the
|
||||
standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Custom Formatter Usage
|
||||
|
||||
The simplest way to make use of the spew custom formatter is to call one of the
|
||||
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
|
||||
functions have syntax you are most likely already familiar with:
|
||||
|
||||
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
spew.Println(myVar, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
|
||||
spew.Fprintf(os.Stderr, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
|
||||
|
||||
See the Index for the full list convenience functions.
|
||||
|
||||
Sample Formatter Output
|
||||
|
||||
Double pointer to a uint8:
|
||||
%v: <**>5
|
||||
%+v: <**>(0xf8400420d0->0xf8400420c8)5
|
||||
%#v: (**uint8)5
|
||||
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
|
||||
|
||||
Pointer to circular struct with a uint8 field and a pointer to itself:
|
||||
%v: <*>{1 <*><shown>}
|
||||
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
|
||||
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
|
||||
%#+v: (*main.circular)(0xf84003e260){ui8:(uint8)1 c:(*main.circular)(0xf84003e260)<shown>}
|
||||
|
||||
See the Printf example for details on the setup of variables being shown
|
||||
here.
|
||||
|
||||
Errors
|
||||
|
||||
Since it is possible for custom Stringer/error interfaces to panic, spew
|
||||
detects them and handles them internally by printing the panic information
|
||||
inline with the output. Since spew is intended to provide deep pretty printing
|
||||
capabilities on structures, it intentionally does not return any errors.
|
||||
*/
|
||||
package spew
|
|
@ -0,0 +1,509 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var (
|
||||
// uint8Type is a reflect.Type representing a uint8. It is used to
|
||||
// convert cgo types to uint8 slices for hexdumping.
|
||||
uint8Type = reflect.TypeOf(uint8(0))
|
||||
|
||||
// cCharRE is a regular expression that matches a cgo char.
|
||||
// It is used to detect character arrays to hexdump them.
|
||||
cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`)
|
||||
|
||||
// cUnsignedCharRE is a regular expression that matches a cgo unsigned
|
||||
// char. It is used to detect unsigned character arrays to hexdump
|
||||
// them.
|
||||
cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`)
|
||||
|
||||
// cUint8tCharRE is a regular expression that matches a cgo uint8_t.
|
||||
// It is used to detect uint8_t arrays to hexdump them.
|
||||
cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`)
|
||||
)
|
||||
|
||||
// dumpState contains information about the state of a dump operation.
|
||||
type dumpState struct {
|
||||
w io.Writer
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
ignoreNextIndent bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// indent performs indentation according to the depth level and cs.Indent
|
||||
// option.
|
||||
func (d *dumpState) indent() {
|
||||
if d.ignoreNextIndent {
|
||||
d.ignoreNextIndent = false
|
||||
return
|
||||
}
|
||||
d.w.Write(bytes.Repeat([]byte(d.cs.Indent), d.depth))
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (d *dumpState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface && !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// dumpPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (d *dumpState) dumpPtr(v reflect.Value) {
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range d.pointers {
|
||||
if depth >= d.depth {
|
||||
delete(d.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by dereferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := d.pointers[addr]; ok && pd < d.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
d.pointers[addr] = d.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type information.
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
d.w.Write([]byte(ve.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
|
||||
// Display pointer information.
|
||||
if !d.cs.DisablePointerAddresses && len(pointerChain) > 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
d.w.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(d.w, addr)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
d.w.Write(openParenBytes)
|
||||
switch {
|
||||
case nilFound:
|
||||
d.w.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
d.w.Write(circularBytes)
|
||||
|
||||
default:
|
||||
d.ignoreNextType = true
|
||||
d.dump(ve)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// dumpSlice handles formatting of arrays and slices. Byte (uint8 under
|
||||
// reflection) arrays and slices are dumped in hexdump -C fashion.
|
||||
func (d *dumpState) dumpSlice(v reflect.Value) {
|
||||
// Determine whether this type should be hex dumped or not. Also,
|
||||
// for types which should be hexdumped, try to use the underlying data
|
||||
// first, then fall back to trying to convert them to a uint8 slice.
|
||||
var buf []uint8
|
||||
doConvert := false
|
||||
doHexDump := false
|
||||
numEntries := v.Len()
|
||||
if numEntries > 0 {
|
||||
vt := v.Index(0).Type()
|
||||
vts := vt.String()
|
||||
switch {
|
||||
// C types that need to be converted.
|
||||
case cCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUnsignedCharRE.MatchString(vts):
|
||||
fallthrough
|
||||
case cUint8tCharRE.MatchString(vts):
|
||||
doConvert = true
|
||||
|
||||
// Try to use existing uint8 slices and fall back to converting
|
||||
// and copying if that fails.
|
||||
case vt.Kind() == reflect.Uint8:
|
||||
// We need an addressable interface to convert the type
|
||||
// to a byte slice. However, the reflect package won't
|
||||
// give us an interface on certain things like
|
||||
// unexported struct fields in order to enforce
|
||||
// visibility rules. We use unsafe, when available, to
|
||||
// bypass these restrictions since this package does not
|
||||
// mutate the values.
|
||||
vs := v
|
||||
if !vs.CanInterface() || !vs.CanAddr() {
|
||||
vs = unsafeReflectValue(vs)
|
||||
}
|
||||
if !UnsafeDisabled {
|
||||
vs = vs.Slice(0, numEntries)
|
||||
|
||||
// Use the existing uint8 slice if it can be
|
||||
// type asserted.
|
||||
iface := vs.Interface()
|
||||
if slice, ok := iface.([]uint8); ok {
|
||||
buf = slice
|
||||
doHexDump = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The underlying data needs to be converted if it can't
|
||||
// be type asserted to a uint8 slice.
|
||||
doConvert = true
|
||||
}
|
||||
|
||||
// Copy and convert the underlying type if needed.
|
||||
if doConvert && vt.ConvertibleTo(uint8Type) {
|
||||
// Convert and copy each element into a uint8 byte
|
||||
// slice.
|
||||
buf = make([]uint8, numEntries)
|
||||
for i := 0; i < numEntries; i++ {
|
||||
vv := v.Index(i)
|
||||
buf[i] = uint8(vv.Convert(uint8Type).Uint())
|
||||
}
|
||||
doHexDump = true
|
||||
}
|
||||
}
|
||||
|
||||
// Hexdump the entire slice as needed.
|
||||
if doHexDump {
|
||||
indent := strings.Repeat(d.cs.Indent, d.depth)
|
||||
str := indent + hex.Dump(buf)
|
||||
str = strings.Replace(str, "\n", "\n"+indent, -1)
|
||||
str = strings.TrimRight(str, d.cs.Indent)
|
||||
d.w.Write([]byte(str))
|
||||
return
|
||||
}
|
||||
|
||||
// Recursively call dump for each item.
|
||||
for i := 0; i < numEntries; i++ {
|
||||
d.dump(d.unpackValue(v.Index(i)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dump is the main workhorse for dumping a value. It uses the passed reflect
|
||||
// value to figure out what kind of object we are dealing with and formats it
|
||||
// appropriately. It is a recursive function, however circular data structures
|
||||
// are detected and handled properly.
|
||||
func (d *dumpState) dump(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
d.w.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
d.indent()
|
||||
d.dumpPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !d.ignoreNextType {
|
||||
d.indent()
|
||||
d.w.Write(openParenBytes)
|
||||
d.w.Write([]byte(v.Type().String()))
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.ignoreNextType = false
|
||||
|
||||
// Display length and capacity if the built-in len and cap functions
|
||||
// work with the value's kind and the len/cap itself is non-zero.
|
||||
valueLen, valueCap := 0, 0
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.Chan:
|
||||
valueLen, valueCap = v.Len(), v.Cap()
|
||||
case reflect.Map, reflect.String:
|
||||
valueLen = v.Len()
|
||||
}
|
||||
if valueLen != 0 || !d.cs.DisableCapacities && valueCap != 0 {
|
||||
d.w.Write(openParenBytes)
|
||||
if valueLen != 0 {
|
||||
d.w.Write(lenEqualsBytes)
|
||||
printInt(d.w, int64(valueLen), 10)
|
||||
}
|
||||
if !d.cs.DisableCapacities && valueCap != 0 {
|
||||
if valueLen != 0 {
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
d.w.Write(capEqualsBytes)
|
||||
printInt(d.w, int64(valueCap), 10)
|
||||
}
|
||||
d.w.Write(closeParenBytes)
|
||||
d.w.Write(spaceBytes)
|
||||
}
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods flag
|
||||
// is enabled
|
||||
if !d.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(d.cs, d.w, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(d.w, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(d.w, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(d.w, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(d.w, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(d.w, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(d.w, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(d.w, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
d.dumpSlice(v)
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.String:
|
||||
d.w.Write([]byte(strconv.Quote(v.String())))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
d.w.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
keys := v.MapKeys()
|
||||
if d.cs.SortKeys {
|
||||
sortValues(keys, d.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
d.dump(d.unpackValue(key))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.MapIndex(key)))
|
||||
if i < (numEntries - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
d.w.Write(openBraceNewlineBytes)
|
||||
d.depth++
|
||||
if (d.cs.MaxDepth != 0) && (d.depth > d.cs.MaxDepth) {
|
||||
d.indent()
|
||||
d.w.Write(maxNewlineBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
numFields := v.NumField()
|
||||
for i := 0; i < numFields; i++ {
|
||||
d.indent()
|
||||
vtf := vt.Field(i)
|
||||
d.w.Write([]byte(vtf.Name))
|
||||
d.w.Write(colonSpaceBytes)
|
||||
d.ignoreNextIndent = true
|
||||
d.dump(d.unpackValue(v.Field(i)))
|
||||
if i < (numFields - 1) {
|
||||
d.w.Write(commaNewlineBytes)
|
||||
} else {
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
d.depth--
|
||||
d.indent()
|
||||
d.w.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(d.w, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(d.w, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it in case any new
|
||||
// types are added.
|
||||
default:
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(d.w, "%v", v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(d.w, "%v", v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fdump is a helper function to consolidate the logic from the various public
|
||||
// methods which take varying writers and config states.
|
||||
func fdump(cs *ConfigState, w io.Writer, a ...interface{}) {
|
||||
for _, arg := range a {
|
||||
if arg == nil {
|
||||
w.Write(interfaceBytes)
|
||||
w.Write(spaceBytes)
|
||||
w.Write(nilAngleBytes)
|
||||
w.Write(newlineBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
d := dumpState{w: w, cs: cs}
|
||||
d.pointers = make(map[uintptr]int)
|
||||
d.dump(reflect.ValueOf(arg))
|
||||
d.w.Write(newlineBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// Fdump formats and displays the passed arguments to io.Writer w. It formats
|
||||
// exactly the same as Dump.
|
||||
func Fdump(w io.Writer, a ...interface{}) {
|
||||
fdump(&Config, w, a...)
|
||||
}
|
||||
|
||||
// Sdump returns a string with the passed arguments formatted exactly the same
|
||||
// as Dump.
|
||||
func Sdump(a ...interface{}) string {
|
||||
var buf bytes.Buffer
|
||||
fdump(&Config, &buf, a...)
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
/*
|
||||
Dump displays the passed parameters to standard out with newlines, customizable
|
||||
indentation, and additional debug information such as complete types and all
|
||||
pointer addresses used to indirect to the final value. It provides the
|
||||
following features over the built-in printing facilities provided by the fmt
|
||||
package:
|
||||
|
||||
* Pointers are dereferenced and followed
|
||||
* Circular data structures are detected and handled properly
|
||||
* Custom Stringer/error interfaces are optionally invoked, including
|
||||
on unexported types
|
||||
* Custom types which only implement the Stringer/error interfaces via
|
||||
a pointer receiver are optionally invoked when passing non-pointer
|
||||
variables
|
||||
* Byte arrays and slices are dumped like the hexdump -C command which
|
||||
includes offsets, byte values in hex, and ASCII output
|
||||
|
||||
The configuration options are controlled by an exported package global,
|
||||
spew.Config. See ConfigState for options documentation.
|
||||
|
||||
See Fdump if you would prefer dumping to an arbitrary io.Writer or Sdump to
|
||||
get the formatted result as a string.
|
||||
*/
|
||||
func Dump(a ...interface{}) {
|
||||
fdump(&Config, os.Stdout, a...)
|
||||
}
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// supportedFlags is a list of all the character flags supported by fmt package.
|
||||
const supportedFlags = "0-+# "
|
||||
|
||||
// formatState implements the fmt.Formatter interface and contains information
|
||||
// about the state of a formatting operation. The NewFormatter function can
|
||||
// be used to get a new Formatter which can be used directly as arguments
|
||||
// in standard fmt package printing calls.
|
||||
type formatState struct {
|
||||
value interface{}
|
||||
fs fmt.State
|
||||
depth int
|
||||
pointers map[uintptr]int
|
||||
ignoreNextType bool
|
||||
cs *ConfigState
|
||||
}
|
||||
|
||||
// buildDefaultFormat recreates the original format string without precision
|
||||
// and width information to pass in to fmt.Sprintf in the case of an
|
||||
// unrecognized type. Unless new types are added to the language, this
|
||||
// function won't ever be called.
|
||||
func (f *formatState) buildDefaultFormat() (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteRune('v')
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// constructOrigFormat recreates the original format string including precision
|
||||
// and width information to pass along to the standard fmt package. This allows
|
||||
// automatic deferral of all format strings this package doesn't support.
|
||||
func (f *formatState) constructOrigFormat(verb rune) (format string) {
|
||||
buf := bytes.NewBuffer(percentBytes)
|
||||
|
||||
for _, flag := range supportedFlags {
|
||||
if f.fs.Flag(int(flag)) {
|
||||
buf.WriteRune(flag)
|
||||
}
|
||||
}
|
||||
|
||||
if width, ok := f.fs.Width(); ok {
|
||||
buf.WriteString(strconv.Itoa(width))
|
||||
}
|
||||
|
||||
if precision, ok := f.fs.Precision(); ok {
|
||||
buf.Write(precisionBytes)
|
||||
buf.WriteString(strconv.Itoa(precision))
|
||||
}
|
||||
|
||||
buf.WriteRune(verb)
|
||||
|
||||
format = buf.String()
|
||||
return format
|
||||
}
|
||||
|
||||
// unpackValue returns values inside of non-nil interfaces when possible and
|
||||
// ensures that types for values which have been unpacked from an interface
|
||||
// are displayed when the show types flag is also set.
|
||||
// This is useful for data types like structs, arrays, slices, and maps which
|
||||
// can contain varying types packed inside an interface.
|
||||
func (f *formatState) unpackValue(v reflect.Value) reflect.Value {
|
||||
if v.Kind() == reflect.Interface {
|
||||
f.ignoreNextType = false
|
||||
if !v.IsNil() {
|
||||
v = v.Elem()
|
||||
}
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// formatPtr handles formatting of pointers by indirecting them as necessary.
|
||||
func (f *formatState) formatPtr(v reflect.Value) {
|
||||
// Display nil if top level pointer is nil.
|
||||
showTypes := f.fs.Flag('#')
|
||||
if v.IsNil() && (!showTypes || f.ignoreNextType) {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Remove pointers at or below the current depth from map used to detect
|
||||
// circular refs.
|
||||
for k, depth := range f.pointers {
|
||||
if depth >= f.depth {
|
||||
delete(f.pointers, k)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep list of all dereferenced pointers to possibly show later.
|
||||
pointerChain := make([]uintptr, 0)
|
||||
|
||||
// Figure out how many levels of indirection there are by derferencing
|
||||
// pointers and unpacking interfaces down the chain while detecting circular
|
||||
// references.
|
||||
nilFound := false
|
||||
cycleFound := false
|
||||
indirects := 0
|
||||
ve := v
|
||||
for ve.Kind() == reflect.Ptr {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
indirects++
|
||||
addr := ve.Pointer()
|
||||
pointerChain = append(pointerChain, addr)
|
||||
if pd, ok := f.pointers[addr]; ok && pd < f.depth {
|
||||
cycleFound = true
|
||||
indirects--
|
||||
break
|
||||
}
|
||||
f.pointers[addr] = f.depth
|
||||
|
||||
ve = ve.Elem()
|
||||
if ve.Kind() == reflect.Interface {
|
||||
if ve.IsNil() {
|
||||
nilFound = true
|
||||
break
|
||||
}
|
||||
ve = ve.Elem()
|
||||
}
|
||||
}
|
||||
|
||||
// Display type or indirection level depending on flags.
|
||||
if showTypes && !f.ignoreNextType {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write(bytes.Repeat(asteriskBytes, indirects))
|
||||
f.fs.Write([]byte(ve.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
} else {
|
||||
if nilFound || cycleFound {
|
||||
indirects += strings.Count(ve.Type().String(), "*")
|
||||
}
|
||||
f.fs.Write(openAngleBytes)
|
||||
f.fs.Write([]byte(strings.Repeat("*", indirects)))
|
||||
f.fs.Write(closeAngleBytes)
|
||||
}
|
||||
|
||||
// Display pointer information depending on flags.
|
||||
if f.fs.Flag('+') && (len(pointerChain) > 0) {
|
||||
f.fs.Write(openParenBytes)
|
||||
for i, addr := range pointerChain {
|
||||
if i > 0 {
|
||||
f.fs.Write(pointerChainBytes)
|
||||
}
|
||||
printHexPtr(f.fs, addr)
|
||||
}
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
|
||||
// Display dereferenced value.
|
||||
switch {
|
||||
case nilFound:
|
||||
f.fs.Write(nilAngleBytes)
|
||||
|
||||
case cycleFound:
|
||||
f.fs.Write(circularShortBytes)
|
||||
|
||||
default:
|
||||
f.ignoreNextType = true
|
||||
f.format(ve)
|
||||
}
|
||||
}
|
||||
|
||||
// format is the main workhorse for providing the Formatter interface. It
|
||||
// uses the passed reflect value to figure out what kind of object we are
|
||||
// dealing with and formats it appropriately. It is a recursive function,
|
||||
// however circular data structures are detected and handled properly.
|
||||
func (f *formatState) format(v reflect.Value) {
|
||||
// Handle invalid reflect values immediately.
|
||||
kind := v.Kind()
|
||||
if kind == reflect.Invalid {
|
||||
f.fs.Write(invalidAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
// Handle pointers specially.
|
||||
if kind == reflect.Ptr {
|
||||
f.formatPtr(v)
|
||||
return
|
||||
}
|
||||
|
||||
// Print type information unless already handled elsewhere.
|
||||
if !f.ignoreNextType && f.fs.Flag('#') {
|
||||
f.fs.Write(openParenBytes)
|
||||
f.fs.Write([]byte(v.Type().String()))
|
||||
f.fs.Write(closeParenBytes)
|
||||
}
|
||||
f.ignoreNextType = false
|
||||
|
||||
// Call Stringer/error interfaces if they exist and the handle methods
|
||||
// flag is enabled.
|
||||
if !f.cs.DisableMethods {
|
||||
if (kind != reflect.Invalid) && (kind != reflect.Interface) {
|
||||
if handled := handleMethods(f.cs, f.fs, v); handled {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.Invalid:
|
||||
// Do nothing. We should never get here since invalid has already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Bool:
|
||||
printBool(f.fs, v.Bool())
|
||||
|
||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int:
|
||||
printInt(f.fs, v.Int(), 10)
|
||||
|
||||
case reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uint:
|
||||
printUint(f.fs, v.Uint(), 10)
|
||||
|
||||
case reflect.Float32:
|
||||
printFloat(f.fs, v.Float(), 32)
|
||||
|
||||
case reflect.Float64:
|
||||
printFloat(f.fs, v.Float(), 64)
|
||||
|
||||
case reflect.Complex64:
|
||||
printComplex(f.fs, v.Complex(), 32)
|
||||
|
||||
case reflect.Complex128:
|
||||
printComplex(f.fs, v.Complex(), 64)
|
||||
|
||||
case reflect.Slice:
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case reflect.Array:
|
||||
f.fs.Write(openBracketBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
numEntries := v.Len()
|
||||
for i := 0; i < numEntries; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.Index(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBracketBytes)
|
||||
|
||||
case reflect.String:
|
||||
f.fs.Write([]byte(v.String()))
|
||||
|
||||
case reflect.Interface:
|
||||
// The only time we should get here is for nil interfaces due to
|
||||
// unpackValue calls.
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
// Do nothing. We should never get here since pointers have already
|
||||
// been handled above.
|
||||
|
||||
case reflect.Map:
|
||||
// nil maps should be indicated as different than empty maps
|
||||
if v.IsNil() {
|
||||
f.fs.Write(nilAngleBytes)
|
||||
break
|
||||
}
|
||||
|
||||
f.fs.Write(openMapBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
keys := v.MapKeys()
|
||||
if f.cs.SortKeys {
|
||||
sortValues(keys, f.cs)
|
||||
}
|
||||
for i, key := range keys {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(key))
|
||||
f.fs.Write(colonBytes)
|
||||
f.ignoreNextType = true
|
||||
f.format(f.unpackValue(v.MapIndex(key)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeMapBytes)
|
||||
|
||||
case reflect.Struct:
|
||||
numFields := v.NumField()
|
||||
f.fs.Write(openBraceBytes)
|
||||
f.depth++
|
||||
if (f.cs.MaxDepth != 0) && (f.depth > f.cs.MaxDepth) {
|
||||
f.fs.Write(maxShortBytes)
|
||||
} else {
|
||||
vt := v.Type()
|
||||
for i := 0; i < numFields; i++ {
|
||||
if i > 0 {
|
||||
f.fs.Write(spaceBytes)
|
||||
}
|
||||
vtf := vt.Field(i)
|
||||
if f.fs.Flag('+') || f.fs.Flag('#') {
|
||||
f.fs.Write([]byte(vtf.Name))
|
||||
f.fs.Write(colonBytes)
|
||||
}
|
||||
f.format(f.unpackValue(v.Field(i)))
|
||||
}
|
||||
}
|
||||
f.depth--
|
||||
f.fs.Write(closeBraceBytes)
|
||||
|
||||
case reflect.Uintptr:
|
||||
printHexPtr(f.fs, uintptr(v.Uint()))
|
||||
|
||||
case reflect.UnsafePointer, reflect.Chan, reflect.Func:
|
||||
printHexPtr(f.fs, v.Pointer())
|
||||
|
||||
// There were not any other types at the time this code was written, but
|
||||
// fall back to letting the default fmt package handle it if any get added.
|
||||
default:
|
||||
format := f.buildDefaultFormat()
|
||||
if v.CanInterface() {
|
||||
fmt.Fprintf(f.fs, format, v.Interface())
|
||||
} else {
|
||||
fmt.Fprintf(f.fs, format, v.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Format satisfies the fmt.Formatter interface. See NewFormatter for usage
|
||||
// details.
|
||||
func (f *formatState) Format(fs fmt.State, verb rune) {
|
||||
f.fs = fs
|
||||
|
||||
// Use standard formatting for verbs that are not v.
|
||||
if verb != 'v' {
|
||||
format := f.constructOrigFormat(verb)
|
||||
fmt.Fprintf(fs, format, f.value)
|
||||
return
|
||||
}
|
||||
|
||||
if f.value == nil {
|
||||
if fs.Flag('#') {
|
||||
fs.Write(interfaceBytes)
|
||||
}
|
||||
fs.Write(nilAngleBytes)
|
||||
return
|
||||
}
|
||||
|
||||
f.format(reflect.ValueOf(f.value))
|
||||
}
|
||||
|
||||
// newFormatter is a helper function to consolidate the logic from the various
|
||||
// public methods which take varying config states.
|
||||
func newFormatter(cs *ConfigState, v interface{}) fmt.Formatter {
|
||||
fs := &formatState{value: v, cs: cs}
|
||||
fs.pointers = make(map[uintptr]int)
|
||||
return fs
|
||||
}
|
||||
|
||||
/*
|
||||
NewFormatter returns a custom formatter that satisfies the fmt.Formatter
|
||||
interface. As a result, it integrates cleanly with standard fmt package
|
||||
printing functions. The formatter is useful for inline printing of smaller data
|
||||
types similar to the standard %v format specifier.
|
||||
|
||||
The custom formatter only responds to the %v (most compact), %+v (adds pointer
|
||||
addresses), %#v (adds types), or %#+v (adds types and pointer addresses) verb
|
||||
combinations. Any other verbs such as %x and %q will be sent to the the
|
||||
standard fmt package for formatting. In addition, the custom formatter ignores
|
||||
the width and precision arguments (however they will still work on the format
|
||||
specifiers not handled by the custom formatter).
|
||||
|
||||
Typically this function shouldn't be called directly. It is much easier to make
|
||||
use of the custom formatter by calling one of the convenience functions such as
|
||||
Printf, Println, or Fprintf.
|
||||
*/
|
||||
func NewFormatter(v interface{}) fmt.Formatter {
|
||||
return newFormatter(&Config, v)
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2013-2016 Dave Collins <dave@davec.name>
|
||||
*
|
||||
* Permission to use, copy, modify, and distribute this software for any
|
||||
* purpose with or without fee is hereby granted, provided that the above
|
||||
* copyright notice and this permission notice appear in all copies.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package spew
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
)
|
||||
|
||||
// Errorf is a wrapper for fmt.Errorf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the formatted string as a value that satisfies error. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Errorf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Errorf(format string, a ...interface{}) (err error) {
|
||||
return fmt.Errorf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprint is a wrapper for fmt.Fprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprint(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprint(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprint(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintf is a wrapper for fmt.Fprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintf(w, format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintf(w, format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Fprintln is a wrapper for fmt.Fprintln that treats each argument as if it
|
||||
// passed with a default Formatter interface returned by NewFormatter. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Fprintln(w, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Fprintln(w io.Writer, a ...interface{}) (n int, err error) {
|
||||
return fmt.Fprintln(w, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Print is a wrapper for fmt.Print that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Print(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Print(a ...interface{}) (n int, err error) {
|
||||
return fmt.Print(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Printf is a wrapper for fmt.Printf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Printf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Printf(format string, a ...interface{}) (n int, err error) {
|
||||
return fmt.Printf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Println is a wrapper for fmt.Println that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the number of bytes written and any write error encountered. See
|
||||
// NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Println(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Println(a ...interface{}) (n int, err error) {
|
||||
return fmt.Println(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprint is a wrapper for fmt.Sprint that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprint(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprint(a ...interface{}) string {
|
||||
return fmt.Sprint(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintf is a wrapper for fmt.Sprintf that treats each argument as if it were
|
||||
// passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintf(format, spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintf(format string, a ...interface{}) string {
|
||||
return fmt.Sprintf(format, convertArgs(a)...)
|
||||
}
|
||||
|
||||
// Sprintln is a wrapper for fmt.Sprintln that treats each argument as if it
|
||||
// were passed with a default Formatter interface returned by NewFormatter. It
|
||||
// returns the resulting string. See NewFormatter for formatting details.
|
||||
//
|
||||
// This function is shorthand for the following syntax:
|
||||
//
|
||||
// fmt.Sprintln(spew.NewFormatter(a), spew.NewFormatter(b))
|
||||
func Sprintln(a ...interface{}) string {
|
||||
return fmt.Sprintln(convertArgs(a)...)
|
||||
}
|
||||
|
||||
// convertArgs accepts a slice of arguments and returns a slice of the same
|
||||
// length with each argument converted to a default spew Formatter interface.
|
||||
func convertArgs(args []interface{}) (formatters []interface{}) {
|
||||
formatters = make([]interface{}, len(args))
|
||||
for index, arg := range args {
|
||||
formatters[index] = NewFormatter(arg)
|
||||
}
|
||||
return formatters
|
||||
}
|
|
@ -531,8 +531,8 @@ Writing Providers and Parsers are easy. See the bundled implementations in the `
|
|||
| 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 |
|
||||
|
|
|
@ -6,6 +6,7 @@ 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
|
||||
|
|
|
@ -9,6 +9,8 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo
|
|||
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=
|
||||
|
|
|
@ -198,7 +198,7 @@ func (ko *Koanf) MergeAt(in *Koanf, path string) {
|
|||
// 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.All())
|
||||
return p.Marshal(ko.Raw())
|
||||
}
|
||||
|
||||
// Unmarshal unmarshals a given key path into the given struct using
|
||||
|
|
|
@ -30,5 +30,5 @@ func (p *TOML) Marshal(o map[string]interface{}) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(out.String()), nil
|
||||
return out.Marshal()
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- "1.11.x"
|
||||
- "1.14.x"
|
||||
- tip
|
||||
|
||||
script:
|
||||
|
|
|
@ -1,3 +1,24 @@
|
|||
## 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
|
||||
|
|
|
@ -100,6 +100,47 @@
|
|||
// "address": "123 Maple St.",
|
||||
// }
|
||||
//
|
||||
// Omit Empty Values
|
||||
//
|
||||
// When decoding from a struct to any other value, you may use the
|
||||
// ",omitempty" suffix on your tag to omit that value if it equates to
|
||||
// the zero value. The zero value of all types is specified in the Go
|
||||
// specification.
|
||||
//
|
||||
// For example, the zero type of a numeric type is zero ("0"). If the struct
|
||||
// field value is zero and a numeric type, the field is empty, and it won't
|
||||
// be encoded into the destination type.
|
||||
//
|
||||
// type Source {
|
||||
// Age int `mapstructure:",omitempty"`
|
||||
// }
|
||||
//
|
||||
// Unexported fields
|
||||
//
|
||||
// Since unexported (private) struct fields cannot be set outside the package
|
||||
// where they are defined, the decoder will simply skip them.
|
||||
//
|
||||
// For this output type definition:
|
||||
//
|
||||
// type Exported struct {
|
||||
// private string // this unexported field will be skipped
|
||||
// Public string
|
||||
// }
|
||||
//
|
||||
// Using this map as input:
|
||||
//
|
||||
// map[string]interface{}{
|
||||
// "private": "I will be ignored",
|
||||
// "Public": "I made it through!",
|
||||
// }
|
||||
//
|
||||
// The following struct will be decoded:
|
||||
//
|
||||
// type Exported struct {
|
||||
// private: "" // field is left with an empty string (zero value)
|
||||
// Public: "I made it through!"
|
||||
// }
|
||||
//
|
||||
// Other Configuration
|
||||
//
|
||||
// mapstructure is highly configurable. See the DecoderConfig struct
|
||||
|
@ -378,6 +419,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
|
||||
var err error
|
||||
outputKind := getKind(outVal)
|
||||
addMetaKey := true
|
||||
switch outputKind {
|
||||
case reflect.Bool:
|
||||
err = d.decodeBool(name, input, outVal)
|
||||
|
@ -396,7 +438,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
case reflect.Map:
|
||||
err = d.decodeMap(name, input, outVal)
|
||||
case reflect.Ptr:
|
||||
err = d.decodePtr(name, input, outVal)
|
||||
addMetaKey, err = d.decodePtr(name, input, outVal)
|
||||
case reflect.Slice:
|
||||
err = d.decodeSlice(name, input, outVal)
|
||||
case reflect.Array:
|
||||
|
@ -410,7 +452,7 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
|
||||
// If we reached here, then we successfully decoded SOMETHING, so
|
||||
// mark the key as used if we're tracking metainput.
|
||||
if d.config.Metadata != nil && name != "" {
|
||||
if addMetaKey && d.config.Metadata != nil && name != "" {
|
||||
d.config.Metadata.Keys = append(d.config.Metadata.Keys, name)
|
||||
}
|
||||
|
||||
|
@ -421,7 +463,34 @@ func (d *Decoder) decode(name string, input interface{}, outVal reflect.Value) e
|
|||
// value to "data" of that type.
|
||||
func (d *Decoder) decodeBasic(name string, data interface{}, val reflect.Value) error {
|
||||
if val.IsValid() && val.Elem().IsValid() {
|
||||
return d.decode(name, data, val.Elem())
|
||||
elem := val.Elem()
|
||||
|
||||
// If we can't address this element, then its not writable. Instead,
|
||||
// we make a copy of the value (which is a pointer and therefore
|
||||
// writable), decode into that, and replace the whole value.
|
||||
copied := false
|
||||
if !elem.CanAddr() {
|
||||
copied = true
|
||||
|
||||
// Make *T
|
||||
copy := reflect.New(elem.Type())
|
||||
|
||||
// *T = elem
|
||||
copy.Elem().Set(elem)
|
||||
|
||||
// Set elem so we decode into it
|
||||
elem = copy
|
||||
}
|
||||
|
||||
// Decode. If we have an error then return. We also return right
|
||||
// away if we're not a copy because that means we decoded directly.
|
||||
if err := d.decode(name, data, elem); err != nil || !copied {
|
||||
return err
|
||||
}
|
||||
|
||||
// If we're a copy, we need to set te final result
|
||||
val.Set(elem.Elem())
|
||||
return nil
|
||||
}
|
||||
|
||||
dataVal := reflect.ValueOf(data)
|
||||
|
@ -798,30 +867,31 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
|||
}
|
||||
|
||||
tagValue := f.Tag.Get(d.config.TagName)
|
||||
tagParts := strings.Split(tagValue, ",")
|
||||
|
||||
// Determine the name of the key in the map
|
||||
keyName := f.Name
|
||||
if tagParts[0] != "" {
|
||||
if tagParts[0] == "-" {
|
||||
continue
|
||||
}
|
||||
keyName = tagParts[0]
|
||||
}
|
||||
|
||||
// If Squash is set in the config, we squash the field down.
|
||||
squash := d.config.Squash && v.Kind() == reflect.Struct
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
if !squash {
|
||||
for _, tag := range tagParts[1:] {
|
||||
if tag == "squash" {
|
||||
squash = true
|
||||
break
|
||||
}
|
||||
squash := d.config.Squash && v.Kind() == reflect.Struct && f.Anonymous
|
||||
// Determine the name of the key in the map
|
||||
if index := strings.Index(tagValue, ","); index != -1 {
|
||||
if tagValue[:index] == "-" {
|
||||
continue
|
||||
}
|
||||
// If "omitempty" is specified in the tag, it ignores empty values.
|
||||
if strings.Index(tagValue[index+1:], "omitempty") != -1 && isEmptyValue(v) {
|
||||
continue
|
||||
}
|
||||
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
squash = !squash && strings.Index(tagValue[index+1:], "squash") != -1
|
||||
if squash && v.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("cannot squash non-struct type '%s'", v.Type())
|
||||
}
|
||||
keyName = tagValue[:index]
|
||||
} else if len(tagValue) > 0 {
|
||||
if tagValue == "-" {
|
||||
continue
|
||||
}
|
||||
keyName = tagValue
|
||||
}
|
||||
|
||||
switch v.Kind() {
|
||||
|
@ -836,11 +906,22 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
|||
mType := reflect.MapOf(vKeyType, vElemType)
|
||||
vMap := reflect.MakeMap(mType)
|
||||
|
||||
err := d.decode(keyName, x.Interface(), vMap)
|
||||
// Creating a pointer to a map so that other methods can completely
|
||||
// overwrite the map if need be (looking at you decodeMapFromMap). The
|
||||
// indirection allows the underlying map to be settable (CanSet() == true)
|
||||
// where as reflect.MakeMap returns an unsettable map.
|
||||
addrVal := reflect.New(vMap.Type())
|
||||
reflect.Indirect(addrVal).Set(vMap)
|
||||
|
||||
err := d.decode(keyName, x.Interface(), reflect.Indirect(addrVal))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// the underlying map may have been completely overwritten so pull
|
||||
// it indirectly out of the enclosing value.
|
||||
vMap = reflect.Indirect(addrVal)
|
||||
|
||||
if squash {
|
||||
for _, k := range vMap.MapKeys() {
|
||||
valMap.SetMapIndex(k, vMap.MapIndex(k))
|
||||
|
@ -861,7 +942,7 @@ func (d *Decoder) decodeMapFromStruct(name string, dataVal reflect.Value, val re
|
|||
return nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) error {
|
||||
func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) (bool, error) {
|
||||
// If the input data is nil, then we want to just set the output
|
||||
// pointer to be nil as well.
|
||||
isNil := data == nil
|
||||
|
@ -882,7 +963,7 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
|
|||
val.Set(nilValue)
|
||||
}
|
||||
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Create an element of the concrete (non pointer) type and decode
|
||||
|
@ -896,16 +977,16 @@ func (d *Decoder) decodePtr(name string, data interface{}, val reflect.Value) er
|
|||
}
|
||||
|
||||
if err := d.decode(name, data, reflect.Indirect(realVal)); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
|
||||
val.Set(realVal)
|
||||
} else {
|
||||
if err := d.decode(name, data, reflect.Indirect(val)); err != nil {
|
||||
return err
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) decodeFunc(name string, data interface{}, val reflect.Value) error {
|
||||
|
@ -1084,13 +1165,23 @@ func (d *Decoder) decodeStruct(name string, data interface{}, val reflect.Value)
|
|||
// Not the most efficient way to do this but we can optimize later if
|
||||
// we want to. To convert from struct to struct we go to map first
|
||||
// as an intermediary.
|
||||
m := make(map[string]interface{})
|
||||
mval := reflect.Indirect(reflect.ValueOf(&m))
|
||||
if err := d.decodeMapFromStruct(name, dataVal, mval, mval); err != nil {
|
||||
|
||||
// Make a new map to hold our result
|
||||
mapType := reflect.TypeOf((map[string]interface{})(nil))
|
||||
mval := reflect.MakeMap(mapType)
|
||||
|
||||
// Creating a pointer to a map so that other methods can completely
|
||||
// overwrite the map if need be (looking at you decodeMapFromMap). The
|
||||
// indirection allows the underlying map to be settable (CanSet() == true)
|
||||
// where as reflect.MakeMap returns an unsettable map.
|
||||
addrVal := reflect.New(mval.Type())
|
||||
|
||||
reflect.Indirect(addrVal).Set(mval)
|
||||
if err := d.decodeMapFromStruct(name, dataVal, reflect.Indirect(addrVal), mval); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
result := d.decodeStructFromMap(name, mval, val)
|
||||
result := d.decodeStructFromMap(name, reflect.Indirect(addrVal), val)
|
||||
return result
|
||||
|
||||
default:
|
||||
|
@ -1144,7 +1235,7 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
fieldKind := fieldType.Type.Kind()
|
||||
|
||||
// If "squash" is specified in the tag, we squash the field down.
|
||||
squash := d.config.Squash && fieldKind == reflect.Struct
|
||||
squash := d.config.Squash && fieldKind == reflect.Struct && fieldType.Anonymous
|
||||
remain := false
|
||||
|
||||
// We always parse the tags cause we're looking for other tags too
|
||||
|
@ -1172,9 +1263,8 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
}
|
||||
|
||||
// Build our field
|
||||
fieldCurrent := field{fieldType, structVal.Field(i)}
|
||||
if remain {
|
||||
remainField = &fieldCurrent
|
||||
remainField = &field{fieldType, structVal.Field(i)}
|
||||
} else {
|
||||
// Normal struct field, store it away
|
||||
fields = append(fields, field{fieldType, structVal.Field(i)})
|
||||
|
@ -1293,6 +1383,24 @@ func (d *Decoder) decodeStructFromMap(name string, dataVal, val reflect.Value) e
|
|||
return nil
|
||||
}
|
||||
|
||||
func isEmptyValue(v reflect.Value) bool {
|
||||
switch getKind(v) {
|
||||
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||
return v.Len() == 0
|
||||
case reflect.Bool:
|
||||
return !v.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return v.Int() == 0
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return v.Uint() == 0
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return v.Float() == 0
|
||||
case reflect.Interface, reflect.Ptr:
|
||||
return v.IsNil()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getKind(val reflect.Value) reflect.Kind {
|
||||
kind := val.Kind()
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Go library for the [TOML](https://github.com/mojombo/toml) format.
|
||||
|
||||
This library supports TOML version
|
||||
[v0.5.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.5.0.md)
|
||||
[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)
|
||||
|
@ -18,7 +18,7 @@ Go-toml provides the following features for using data parsed from TOML document
|
|||
|
||||
* Load TOML documents from files and string data
|
||||
* Easily navigate TOML structure using Tree
|
||||
* Mashaling and unmarshaling to and from data structures
|
||||
* 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
|
||||
|
@ -74,7 +74,7 @@ Or use a query:
|
|||
q, _ := query.Compile("$..[user,password]")
|
||||
results := q.Execute(config)
|
||||
for ii, item := range results.Values() {
|
||||
fmt.Println("Query result %d: %v", ii, item)
|
||||
fmt.Printf("Query result %d: %v\n", ii, item)
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -87,7 +87,7 @@ The documentation and additional examples are available at
|
|||
|
||||
Go-toml provides two handy command line tools:
|
||||
|
||||
* `tomll`: Reads TOML files and lint them.
|
||||
* `tomll`: Reads TOML files and lints them.
|
||||
|
||||
```
|
||||
go install github.com/pelletier/go-toml/cmd/tomll
|
||||
|
@ -99,9 +99,9 @@ Go-toml provides two handy command line tools:
|
|||
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
|
||||
|
|
|
@ -13,9 +13,9 @@ stages:
|
|||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: "Install Go 1.14"
|
||||
displayName: "Install Go 1.15"
|
||||
inputs:
|
||||
version: "1.14"
|
||||
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
|
||||
|
@ -36,9 +36,9 @@ stages:
|
|||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: "Install Go 1.14"
|
||||
displayName: "Install Go 1.15"
|
||||
inputs:
|
||||
version: "1.14"
|
||||
version: "1.15"
|
||||
- task: Go@0
|
||||
displayName: "go fmt ./..."
|
||||
inputs:
|
||||
|
@ -51,9 +51,9 @@ stages:
|
|||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: "Install Go 1.14"
|
||||
displayName: "Install Go 1.15"
|
||||
inputs:
|
||||
version: "1.14"
|
||||
version: "1.15"
|
||||
- task: Go@0
|
||||
displayName: "Generate coverage"
|
||||
inputs:
|
||||
|
@ -71,9 +71,9 @@ stages:
|
|||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: "Install Go 1.14"
|
||||
displayName: "Install Go 1.15"
|
||||
inputs:
|
||||
version: "1.14"
|
||||
version: "1.15"
|
||||
- script: echo "##vso[task.setvariable variable=PATH]${PATH}:/home/vsts/go/bin/"
|
||||
- task: Bash@3
|
||||
inputs:
|
||||
|
@ -86,9 +86,9 @@ stages:
|
|||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- task: GoTool@0
|
||||
displayName: "Install Go 1.14"
|
||||
displayName: "Install Go 1.15"
|
||||
inputs:
|
||||
version: "1.14"
|
||||
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
|
||||
|
@ -102,6 +102,15 @@ stages:
|
|||
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'
|
||||
|
@ -111,15 +120,6 @@ stages:
|
|||
windows 1.14:
|
||||
goVersion: '1.14'
|
||||
imageName: 'windows-latest'
|
||||
linux 1.13:
|
||||
goVersion: '1.13'
|
||||
imageName: 'ubuntu-latest'
|
||||
mac 1.13:
|
||||
goVersion: '1.13'
|
||||
imageName: 'macOS-latest'
|
||||
windows 1.13:
|
||||
goVersion: '1.13'
|
||||
imageName: 'windows-latest'
|
||||
pool:
|
||||
vmImage: $(imageName)
|
||||
steps:
|
||||
|
@ -155,7 +155,7 @@ stages:
|
|||
- task: GoTool@0
|
||||
displayName: "Install Go"
|
||||
inputs:
|
||||
version: 1.14
|
||||
version: 1.15
|
||||
- task: Bash@3
|
||||
inputs:
|
||||
targetType: inline
|
||||
|
|
|
@ -1,164 +0,0 @@
|
|||
{
|
||||
"array": {
|
||||
"key1": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"key2": [
|
||||
"red",
|
||||
"yellow",
|
||||
"green"
|
||||
],
|
||||
"key3": [
|
||||
[
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
3,
|
||||
4,
|
||||
5
|
||||
]
|
||||
],
|
||||
"key4": [
|
||||
[
|
||||
1,
|
||||
2
|
||||
],
|
||||
[
|
||||
"a",
|
||||
"b",
|
||||
"c"
|
||||
]
|
||||
],
|
||||
"key5": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"key6": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"boolean": {
|
||||
"False": false,
|
||||
"True": true
|
||||
},
|
||||
"datetime": {
|
||||
"key1": "1979-05-27T07:32:00Z",
|
||||
"key2": "1979-05-27T00:32:00-07:00",
|
||||
"key3": "1979-05-27T00:32:00.999999-07:00"
|
||||
},
|
||||
"float": {
|
||||
"both": {
|
||||
"key": 6.626e-34
|
||||
},
|
||||
"exponent": {
|
||||
"key1": 5e+22,
|
||||
"key2": 1000000,
|
||||
"key3": -0.02
|
||||
},
|
||||
"fractional": {
|
||||
"key1": 1,
|
||||
"key2": 3.1415,
|
||||
"key3": -0.01
|
||||
},
|
||||
"underscores": {
|
||||
"key1": 9224617.445991227,
|
||||
"key2": 1e+100
|
||||
}
|
||||
},
|
||||
"fruit": [{
|
||||
"name": "apple",
|
||||
"physical": {
|
||||
"color": "red",
|
||||
"shape": "round"
|
||||
},
|
||||
"variety": [{
|
||||
"name": "red delicious"
|
||||
},
|
||||
{
|
||||
"name": "granny smith"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "banana",
|
||||
"variety": [{
|
||||
"name": "plantain"
|
||||
}]
|
||||
}
|
||||
],
|
||||
"integer": {
|
||||
"key1": 99,
|
||||
"key2": 42,
|
||||
"key3": 0,
|
||||
"key4": -17,
|
||||
"underscores": {
|
||||
"key1": 1000,
|
||||
"key2": 5349221,
|
||||
"key3": 12345
|
||||
}
|
||||
},
|
||||
"products": [{
|
||||
"name": "Hammer",
|
||||
"sku": 738594937
|
||||
},
|
||||
{},
|
||||
{
|
||||
"color": "gray",
|
||||
"name": "Nail",
|
||||
"sku": 284758393
|
||||
}
|
||||
],
|
||||
"string": {
|
||||
"basic": {
|
||||
"basic": "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
|
||||
},
|
||||
"literal": {
|
||||
"multiline": {
|
||||
"lines": "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n",
|
||||
"regex2": "I [dw]on't need \\d{2} apples"
|
||||
},
|
||||
"quoted": "Tom \"Dubs\" Preston-Werner",
|
||||
"regex": "\u003c\\i\\c*\\s*\u003e",
|
||||
"winpath": "C:\\Users\\nodejs\\templates",
|
||||
"winpath2": "\\\\ServerX\\admin$\\system32\\"
|
||||
},
|
||||
"multiline": {
|
||||
"continued": {
|
||||
"key1": "The quick brown fox jumps over the lazy dog.",
|
||||
"key2": "The quick brown fox jumps over the lazy dog.",
|
||||
"key3": "The quick brown fox jumps over the lazy dog."
|
||||
},
|
||||
"key1": "One\nTwo",
|
||||
"key2": "One\nTwo",
|
||||
"key3": "One\nTwo"
|
||||
}
|
||||
},
|
||||
"table": {
|
||||
"inline": {
|
||||
"name": {
|
||||
"first": "Tom",
|
||||
"last": "Preston-Werner"
|
||||
},
|
||||
"point": {
|
||||
"x": 1,
|
||||
"y": 2
|
||||
}
|
||||
},
|
||||
"key": "value",
|
||||
"subtable": {
|
||||
"key": "another value"
|
||||
}
|
||||
},
|
||||
"x": {
|
||||
"y": {
|
||||
"z": {
|
||||
"w": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,11 +20,15 @@ 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"
|
||||
|
|
|
@ -1,244 +0,0 @@
|
|||
################################################################################
|
||||
## Comment
|
||||
|
||||
# Speak your mind with the hash symbol. They go from the symbol to the end of
|
||||
# the line.
|
||||
|
||||
|
||||
################################################################################
|
||||
## Table
|
||||
|
||||
# Tables (also known as hash tables or dictionaries) are collections of
|
||||
# key/value pairs. They appear in square brackets on a line by themselves.
|
||||
|
||||
[table]
|
||||
|
||||
key = "value" # Yeah, you can do this.
|
||||
|
||||
# Nested tables are denoted by table names with dots in them. Name your tables
|
||||
# whatever crap you please, just don't use #, ., [ or ].
|
||||
|
||||
[table.subtable]
|
||||
|
||||
key = "another value"
|
||||
|
||||
# You don't need to specify all the super-tables if you don't want to. TOML
|
||||
# knows how to do it for you.
|
||||
|
||||
# [x] you
|
||||
# [x.y] don't
|
||||
# [x.y.z] need these
|
||||
[x.y.z.w] # for this to work
|
||||
|
||||
|
||||
################################################################################
|
||||
## Inline Table
|
||||
|
||||
# Inline tables provide a more compact syntax for expressing tables. They are
|
||||
# especially useful for grouped data that can otherwise quickly become verbose.
|
||||
# Inline tables are enclosed in curly braces `{` and `}`. No newlines are
|
||||
# allowed between the curly braces unless they are valid within a value.
|
||||
|
||||
[table.inline]
|
||||
|
||||
name = { first = "Tom", last = "Preston-Werner" }
|
||||
point = { x = 1, y = 2 }
|
||||
|
||||
|
||||
################################################################################
|
||||
## String
|
||||
|
||||
# There are four ways to express strings: basic, multi-line basic, literal, and
|
||||
# multi-line literal. All strings must contain only valid UTF-8 characters.
|
||||
|
||||
[string.basic]
|
||||
|
||||
basic = "I'm a string. \"You can quote me\". Name\tJos\u00E9\nLocation\tSF."
|
||||
|
||||
[string.multiline]
|
||||
|
||||
# The following strings are byte-for-byte equivalent:
|
||||
key1 = "One\nTwo"
|
||||
key2 = """One\nTwo"""
|
||||
key3 = """
|
||||
One
|
||||
Two"""
|
||||
|
||||
[string.multiline.continued]
|
||||
|
||||
# The following strings are byte-for-byte equivalent:
|
||||
key1 = "The quick brown fox jumps over the lazy dog."
|
||||
|
||||
key2 = """
|
||||
The quick brown \
|
||||
|
||||
|
||||
fox jumps over \
|
||||
the lazy dog."""
|
||||
|
||||
key3 = """\
|
||||
The quick brown \
|
||||
fox jumps over \
|
||||
the lazy dog.\
|
||||
"""
|
||||
|
||||
[string.literal]
|
||||
|
||||
# What you see is what you get.
|
||||
winpath = 'C:\Users\nodejs\templates'
|
||||
winpath2 = '\\ServerX\admin$\system32\'
|
||||
quoted = 'Tom "Dubs" Preston-Werner'
|
||||
regex = '<\i\c*\s*>'
|
||||
|
||||
|
||||
[string.literal.multiline]
|
||||
|
||||
regex2 = '''I [dw]on't need \d{2} apples'''
|
||||
lines = '''
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
'''
|
||||
|
||||
|
||||
################################################################################
|
||||
## Integer
|
||||
|
||||
# Integers are whole numbers. Positive numbers may be prefixed with a plus sign.
|
||||
# Negative numbers are prefixed with a minus sign.
|
||||
|
||||
[integer]
|
||||
|
||||
key1 = +99
|
||||
key2 = 42
|
||||
key3 = 0
|
||||
key4 = -17
|
||||
|
||||
[integer.underscores]
|
||||
|
||||
# For large numbers, you may use underscores to enhance readability. Each
|
||||
# underscore must be surrounded by at least one digit.
|
||||
key1 = 1_000
|
||||
key2 = 5_349_221
|
||||
key3 = 1_2_3_4_5 # valid but inadvisable
|
||||
|
||||
|
||||
################################################################################
|
||||
## Float
|
||||
|
||||
# A float consists of an integer part (which may be prefixed with a plus or
|
||||
# minus sign) followed by a fractional part and/or an exponent part.
|
||||
|
||||
[float.fractional]
|
||||
|
||||
key1 = +1.0
|
||||
key2 = 3.1415
|
||||
key3 = -0.01
|
||||
|
||||
[float.exponent]
|
||||
|
||||
key1 = 5e+22
|
||||
key2 = 1e6
|
||||
key3 = -2E-2
|
||||
|
||||
[float.both]
|
||||
|
||||
key = 6.626e-34
|
||||
|
||||
[float.underscores]
|
||||
|
||||
key1 = 9_224_617.445_991_228_313
|
||||
key2 = 1e1_00
|
||||
|
||||
|
||||
################################################################################
|
||||
## Boolean
|
||||
|
||||
# Booleans are just the tokens you're used to. Always lowercase.
|
||||
|
||||
[boolean]
|
||||
|
||||
True = true
|
||||
False = false
|
||||
|
||||
|
||||
################################################################################
|
||||
## Datetime
|
||||
|
||||
# Datetimes are RFC 3339 dates.
|
||||
|
||||
[datetime]
|
||||
|
||||
key1 = 1979-05-27T07:32:00Z
|
||||
key2 = 1979-05-27T00:32:00-07:00
|
||||
key3 = 1979-05-27T00:32:00.999999-07:00
|
||||
|
||||
|
||||
################################################################################
|
||||
## Array
|
||||
|
||||
# Arrays are square brackets with other primitives inside. Whitespace is
|
||||
# ignored. Elements are separated by commas. Data types may not be mixed.
|
||||
|
||||
[array]
|
||||
|
||||
key1 = [ 1, 2, 3 ]
|
||||
key2 = [ "red", "yellow", "green" ]
|
||||
key3 = [ [ 1, 2 ], [3, 4, 5] ]
|
||||
#key4 = [ [ 1, 2 ], ["a", "b", "c"] ] # this is ok
|
||||
|
||||
# Arrays can also be multiline. So in addition to ignoring whitespace, arrays
|
||||
# also ignore newlines between the brackets. Terminating commas are ok before
|
||||
# the closing bracket.
|
||||
|
||||
key5 = [
|
||||
1, 2, 3
|
||||
]
|
||||
key6 = [
|
||||
1,
|
||||
2, # this is ok
|
||||
]
|
||||
|
||||
|
||||
################################################################################
|
||||
## Array of Tables
|
||||
|
||||
# These can be expressed by using a table name in double brackets. Each table
|
||||
# with the same double bracketed name will be an element in the array. The
|
||||
# tables are inserted in the order encountered.
|
||||
|
||||
[[products]]
|
||||
|
||||
name = "Hammer"
|
||||
sku = 738594937
|
||||
|
||||
[[products]]
|
||||
|
||||
[[products]]
|
||||
|
||||
name = "Nail"
|
||||
sku = 284758393
|
||||
color = "gray"
|
||||
|
||||
|
||||
# You can create nested arrays of tables as well.
|
||||
|
||||
[[fruit]]
|
||||
name = "apple"
|
||||
|
||||
[fruit.physical]
|
||||
color = "red"
|
||||
shape = "round"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "red delicious"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "granny smith"
|
||||
|
||||
[[fruit]]
|
||||
name = "banana"
|
||||
|
||||
[[fruit.variety]]
|
||||
name = "plantain"
|
|
@ -1,121 +0,0 @@
|
|||
---
|
||||
array:
|
||||
key1:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
key2:
|
||||
- red
|
||||
- yellow
|
||||
- green
|
||||
key3:
|
||||
- - 1
|
||||
- 2
|
||||
- - 3
|
||||
- 4
|
||||
- 5
|
||||
key4:
|
||||
- - 1
|
||||
- 2
|
||||
- - a
|
||||
- b
|
||||
- c
|
||||
key5:
|
||||
- 1
|
||||
- 2
|
||||
- 3
|
||||
key6:
|
||||
- 1
|
||||
- 2
|
||||
boolean:
|
||||
'False': false
|
||||
'True': true
|
||||
datetime:
|
||||
key1: '1979-05-27T07:32:00Z'
|
||||
key2: '1979-05-27T00:32:00-07:00'
|
||||
key3: '1979-05-27T00:32:00.999999-07:00'
|
||||
float:
|
||||
both:
|
||||
key: 6.626e-34
|
||||
exponent:
|
||||
key1: 5.0e+22
|
||||
key2: 1000000
|
||||
key3: -0.02
|
||||
fractional:
|
||||
key1: 1
|
||||
key2: 3.1415
|
||||
key3: -0.01
|
||||
underscores:
|
||||
key1: 9224617.445991227
|
||||
key2: 1.0e+100
|
||||
fruit:
|
||||
- name: apple
|
||||
physical:
|
||||
color: red
|
||||
shape: round
|
||||
variety:
|
||||
- name: red delicious
|
||||
- name: granny smith
|
||||
- name: banana
|
||||
variety:
|
||||
- name: plantain
|
||||
integer:
|
||||
key1: 99
|
||||
key2: 42
|
||||
key3: 0
|
||||
key4: -17
|
||||
underscores:
|
||||
key1: 1000
|
||||
key2: 5349221
|
||||
key3: 12345
|
||||
products:
|
||||
- name: Hammer
|
||||
sku: 738594937
|
||||
- {}
|
||||
- color: gray
|
||||
name: Nail
|
||||
sku: 284758393
|
||||
string:
|
||||
basic:
|
||||
basic: "I'm a string. \"You can quote me\". Name\tJosé\nLocation\tSF."
|
||||
literal:
|
||||
multiline:
|
||||
lines: |
|
||||
The first newline is
|
||||
trimmed in raw strings.
|
||||
All other whitespace
|
||||
is preserved.
|
||||
regex2: I [dw]on't need \d{2} apples
|
||||
quoted: Tom "Dubs" Preston-Werner
|
||||
regex: "<\\i\\c*\\s*>"
|
||||
winpath: C:\Users\nodejs\templates
|
||||
winpath2: "\\\\ServerX\\admin$\\system32\\"
|
||||
multiline:
|
||||
continued:
|
||||
key1: The quick brown fox jumps over the lazy dog.
|
||||
key2: The quick brown fox jumps over the lazy dog.
|
||||
key3: The quick brown fox jumps over the lazy dog.
|
||||
key1: |-
|
||||
One
|
||||
Two
|
||||
key2: |-
|
||||
One
|
||||
Two
|
||||
key3: |-
|
||||
One
|
||||
Two
|
||||
table:
|
||||
inline:
|
||||
name:
|
||||
first: Tom
|
||||
last: Preston-Werner
|
||||
point:
|
||||
x: 1
|
||||
y: 2
|
||||
key: value
|
||||
subtable:
|
||||
key: another value
|
||||
x:
|
||||
y:
|
||||
z:
|
||||
w: {}
|
|
@ -27,3 +27,4 @@ enabled = true
|
|||
|
||||
[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
|
|
@ -27,3 +27,4 @@ enabled = true
|
|||
|
||||
[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
|
|
@ -2,8 +2,4 @@ module github.com/pelletier/go-toml
|
|||
|
||||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v0.3.1
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
)
|
||||
require github.com/davecgh/go-spew v1.1.1
|
||||
|
|
|
@ -15,3 +15,5 @@ gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
|||
gopkg.in/yaml.v2 v2.2.7/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=
|
||||
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
|
||||
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
|
|
|
@ -5,7 +5,6 @@ package toml
|
|||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Convert the bare key group string to an array.
|
||||
|
@ -109,5 +108,5 @@ func parseKey(key string) ([]string, error) {
|
|||
}
|
||||
|
||||
func isValidBareChar(r rune) bool {
|
||||
return isAlphanumeric(r) || r == '-' || unicode.IsNumber(r)
|
||||
return isAlphanumeric(r) || r == '-' || isDigit(r)
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ type tomlLexer struct {
|
|||
currentTokenStart int
|
||||
currentTokenStop int
|
||||
tokens []token
|
||||
depth int
|
||||
brackets []rune
|
||||
line int
|
||||
col int
|
||||
endbufferLine int
|
||||
|
@ -123,6 +123,8 @@ func (l *tomlLexer) lexVoid() tomlLexStateFn {
|
|||
for {
|
||||
next := l.peek()
|
||||
switch next {
|
||||
case '}': // after '{'
|
||||
return l.lexRightCurlyBrace
|
||||
case '[':
|
||||
return l.lexTableKey
|
||||
case '#':
|
||||
|
@ -140,10 +142,6 @@ func (l *tomlLexer) lexVoid() tomlLexStateFn {
|
|||
l.skip()
|
||||
}
|
||||
|
||||
if l.depth > 0 {
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
if isKeyStartChar(next) {
|
||||
return l.lexKey
|
||||
}
|
||||
|
@ -167,10 +165,8 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
|||
case '=':
|
||||
return l.lexEqual
|
||||
case '[':
|
||||
l.depth++
|
||||
return l.lexLeftBracket
|
||||
case ']':
|
||||
l.depth--
|
||||
return l.lexRightBracket
|
||||
case '{':
|
||||
return l.lexLeftCurlyBrace
|
||||
|
@ -188,12 +184,10 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
|||
fallthrough
|
||||
case '\n':
|
||||
l.skip()
|
||||
if l.depth == 0 {
|
||||
return l.lexVoid
|
||||
if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '[' {
|
||||
return l.lexRvalue
|
||||
}
|
||||
return l.lexRvalue
|
||||
case '_':
|
||||
return l.errorf("cannot start number with underscore")
|
||||
return l.lexVoid
|
||||
}
|
||||
|
||||
if l.follow("true") {
|
||||
|
@ -236,10 +230,6 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
|||
return l.lexNumber
|
||||
}
|
||||
|
||||
if isAlphanumeric(next) {
|
||||
return l.lexKey
|
||||
}
|
||||
|
||||
return l.errorf("no value can start with %c", next)
|
||||
}
|
||||
|
||||
|
@ -250,12 +240,17 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
|
|||
func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn {
|
||||
l.next()
|
||||
l.emit(tokenLeftCurlyBrace)
|
||||
l.brackets = append(l.brackets, '{')
|
||||
return l.lexVoid
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn {
|
||||
l.next()
|
||||
l.emit(tokenRightCurlyBrace)
|
||||
if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '{' {
|
||||
return l.errorf("cannot have '}' here")
|
||||
}
|
||||
l.brackets = l.brackets[:len(l.brackets)-1]
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
|
@ -302,13 +297,16 @@ func (l *tomlLexer) lexEqual() tomlLexStateFn {
|
|||
func (l *tomlLexer) lexComma() tomlLexStateFn {
|
||||
l.next()
|
||||
l.emit(tokenComma)
|
||||
if len(l.brackets) > 0 && l.brackets[len(l.brackets)-1] == '{' {
|
||||
return l.lexVoid
|
||||
}
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
// Parse the key and emits its value without escape sequences.
|
||||
// bare keys, basic string keys and literal string keys are supported.
|
||||
func (l *tomlLexer) lexKey() tomlLexStateFn {
|
||||
growingString := ""
|
||||
var sb strings.Builder
|
||||
|
||||
for r := l.peek(); isKeyChar(r) || r == '\n' || r == '\r'; r = l.peek() {
|
||||
if r == '"' {
|
||||
|
@ -317,7 +315,9 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
|
|||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
growingString += "\"" + str + "\""
|
||||
sb.WriteString("\"")
|
||||
sb.WriteString(str)
|
||||
sb.WriteString("\"")
|
||||
l.next()
|
||||
continue
|
||||
} else if r == '\'' {
|
||||
|
@ -326,22 +326,45 @@ func (l *tomlLexer) lexKey() tomlLexStateFn {
|
|||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
growingString += "'" + str + "'"
|
||||
sb.WriteString("'")
|
||||
sb.WriteString(str)
|
||||
sb.WriteString("'")
|
||||
l.next()
|
||||
continue
|
||||
} else if r == '\n' {
|
||||
return l.errorf("keys cannot contain new lines")
|
||||
} else if isSpace(r) {
|
||||
break
|
||||
var str strings.Builder
|
||||
str.WriteString(" ")
|
||||
|
||||
// skip trailing whitespace
|
||||
l.next()
|
||||
for r = l.peek(); isSpace(r); r = l.peek() {
|
||||
str.WriteRune(r)
|
||||
l.next()
|
||||
}
|
||||
// break loop if not a dot
|
||||
if r != '.' {
|
||||
break
|
||||
}
|
||||
str.WriteString(".")
|
||||
// skip trailing whitespace after dot
|
||||
l.next()
|
||||
for r = l.peek(); isSpace(r); r = l.peek() {
|
||||
str.WriteRune(r)
|
||||
l.next()
|
||||
}
|
||||
sb.WriteString(str.String())
|
||||
continue
|
||||
} else if r == '.' {
|
||||
// skip
|
||||
} else if !isValidBareChar(r) {
|
||||
return l.errorf("keys cannot contain %c character", r)
|
||||
}
|
||||
growingString += string(r)
|
||||
sb.WriteRune(r)
|
||||
l.next()
|
||||
}
|
||||
l.emitWithValue(tokenKey, growingString)
|
||||
l.emitWithValue(tokenKey, sb.String())
|
||||
return l.lexVoid
|
||||
}
|
||||
|
||||
|
@ -361,11 +384,12 @@ func (l *tomlLexer) lexComment(previousState tomlLexStateFn) tomlLexStateFn {
|
|||
func (l *tomlLexer) lexLeftBracket() tomlLexStateFn {
|
||||
l.next()
|
||||
l.emit(tokenLeftBracket)
|
||||
l.brackets = append(l.brackets, '[')
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNewLine bool) (string, error) {
|
||||
growingString := ""
|
||||
var sb strings.Builder
|
||||
|
||||
if discardLeadingNewLine {
|
||||
if l.follow("\r\n") {
|
||||
|
@ -379,14 +403,14 @@ func (l *tomlLexer) lexLiteralStringAsString(terminator string, discardLeadingNe
|
|||
// find end of string
|
||||
for {
|
||||
if l.follow(terminator) {
|
||||
return growingString, nil
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
next := l.peek()
|
||||
if next == eof {
|
||||
break
|
||||
}
|
||||
growingString += string(l.next())
|
||||
sb.WriteRune(l.next())
|
||||
}
|
||||
|
||||
return "", errors.New("unclosed string")
|
||||
|
@ -420,7 +444,7 @@ func (l *tomlLexer) lexLiteralString() tomlLexStateFn {
|
|||
// Terminator is the substring indicating the end of the token.
|
||||
// The resulting string does not include the terminator.
|
||||
func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine, acceptNewLines bool) (string, error) {
|
||||
growingString := ""
|
||||
var sb strings.Builder
|
||||
|
||||
if discardLeadingNewLine {
|
||||
if l.follow("\r\n") {
|
||||
|
@ -433,7 +457,7 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||
|
||||
for {
|
||||
if l.follow(terminator) {
|
||||
return growingString, nil
|
||||
return sb.String(), nil
|
||||
}
|
||||
|
||||
if l.follow("\\") {
|
||||
|
@ -451,72 +475,72 @@ func (l *tomlLexer) lexStringAsString(terminator string, discardLeadingNewLine,
|
|||
l.next()
|
||||
}
|
||||
case '"':
|
||||
growingString += "\""
|
||||
sb.WriteString("\"")
|
||||
l.next()
|
||||
case 'n':
|
||||
growingString += "\n"
|
||||
sb.WriteString("\n")
|
||||
l.next()
|
||||
case 'b':
|
||||
growingString += "\b"
|
||||
sb.WriteString("\b")
|
||||
l.next()
|
||||
case 'f':
|
||||
growingString += "\f"
|
||||
sb.WriteString("\f")
|
||||
l.next()
|
||||
case '/':
|
||||
growingString += "/"
|
||||
sb.WriteString("/")
|
||||
l.next()
|
||||
case 't':
|
||||
growingString += "\t"
|
||||
sb.WriteString("\t")
|
||||
l.next()
|
||||
case 'r':
|
||||
growingString += "\r"
|
||||
sb.WriteString("\r")
|
||||
l.next()
|
||||
case '\\':
|
||||
growingString += "\\"
|
||||
sb.WriteString("\\")
|
||||
l.next()
|
||||
case 'u':
|
||||
l.next()
|
||||
code := ""
|
||||
var code strings.Builder
|
||||
for i := 0; i < 4; i++ {
|
||||
c := l.peek()
|
||||
if !isHexDigit(c) {
|
||||
return "", errors.New("unfinished unicode escape")
|
||||
}
|
||||
l.next()
|
||||
code = code + string(c)
|
||||
code.WriteRune(c)
|
||||
}
|
||||
intcode, err := strconv.ParseInt(code, 16, 32)
|
||||
intcode, err := strconv.ParseInt(code.String(), 16, 32)
|
||||
if err != nil {
|
||||
return "", errors.New("invalid unicode escape: \\u" + code)
|
||||
return "", errors.New("invalid unicode escape: \\u" + code.String())
|
||||
}
|
||||
growingString += string(rune(intcode))
|
||||
sb.WriteRune(rune(intcode))
|
||||
case 'U':
|
||||
l.next()
|
||||
code := ""
|
||||
var code strings.Builder
|
||||
for i := 0; i < 8; i++ {
|
||||
c := l.peek()
|
||||
if !isHexDigit(c) {
|
||||
return "", errors.New("unfinished unicode escape")
|
||||
}
|
||||
l.next()
|
||||
code = code + string(c)
|
||||
code.WriteRune(c)
|
||||
}
|
||||
intcode, err := strconv.ParseInt(code, 16, 64)
|
||||
intcode, err := strconv.ParseInt(code.String(), 16, 64)
|
||||
if err != nil {
|
||||
return "", errors.New("invalid unicode escape: \\U" + code)
|
||||
return "", errors.New("invalid unicode escape: \\U" + code.String())
|
||||
}
|
||||
growingString += string(rune(intcode))
|
||||
sb.WriteRune(rune(intcode))
|
||||
default:
|
||||
return "", errors.New("invalid escape sequence: \\" + string(l.peek()))
|
||||
}
|
||||
} else {
|
||||
r := l.peek()
|
||||
|
||||
if 0x00 <= r && r <= 0x1F && !(acceptNewLines && (r == '\n' || r == '\r')) {
|
||||
if 0x00 <= r && r <= 0x1F && r != '\t' && !(acceptNewLines && (r == '\n' || r == '\r')) {
|
||||
return "", fmt.Errorf("unescaped control character %U", r)
|
||||
}
|
||||
l.next()
|
||||
growingString += string(r)
|
||||
sb.WriteRune(r)
|
||||
}
|
||||
|
||||
if l.peek() == eof {
|
||||
|
@ -543,7 +567,6 @@ func (l *tomlLexer) lexString() tomlLexStateFn {
|
|||
}
|
||||
|
||||
str, err := l.lexStringAsString(terminator, discardLeadingNewLine, acceptNewLines)
|
||||
|
||||
if err != nil {
|
||||
return l.errorf(err.Error())
|
||||
}
|
||||
|
@ -615,6 +638,10 @@ func (l *tomlLexer) lexInsideTableKey() tomlLexStateFn {
|
|||
func (l *tomlLexer) lexRightBracket() tomlLexStateFn {
|
||||
l.next()
|
||||
l.emit(tokenRightBracket)
|
||||
if len(l.brackets) == 0 || l.brackets[len(l.brackets)-1] != '[' {
|
||||
return l.errorf("cannot have ']' here")
|
||||
}
|
||||
l.brackets = l.brackets[:len(l.brackets)-1]
|
||||
return l.lexRvalue
|
||||
}
|
||||
|
||||
|
@ -748,19 +775,19 @@ func init() {
|
|||
// /!\ also matches the empty string
|
||||
//
|
||||
// Example matches:
|
||||
//1979-05-27T07:32:00Z
|
||||
//1979-05-27T00:32:00-07:00
|
||||
//1979-05-27T00:32:00.999999-07:00
|
||||
//1979-05-27 07:32:00Z
|
||||
//1979-05-27 00:32:00-07:00
|
||||
//1979-05-27 00:32:00.999999-07:00
|
||||
//1979-05-27T07:32:00
|
||||
//1979-05-27T00:32:00.999999
|
||||
//1979-05-27 07:32:00
|
||||
//1979-05-27 00:32:00.999999
|
||||
//1979-05-27
|
||||
//07:32:00
|
||||
//00:32:00.999999
|
||||
// 1979-05-27T07:32:00Z
|
||||
// 1979-05-27T00:32:00-07:00
|
||||
// 1979-05-27T00:32:00.999999-07:00
|
||||
// 1979-05-27 07:32:00Z
|
||||
// 1979-05-27 00:32:00-07:00
|
||||
// 1979-05-27 00:32:00.999999-07:00
|
||||
// 1979-05-27T07:32:00
|
||||
// 1979-05-27T00:32:00.999999
|
||||
// 1979-05-27 07:32:00
|
||||
// 1979-05-27 00:32:00.999999
|
||||
// 1979-05-27
|
||||
// 07:32:00
|
||||
// 00:32:00.999999
|
||||
dateRegexp = regexp.MustCompile(`^(?:\d{1,4}-\d{2}-\d{2})?(?:[T ]?\d{2}:\d{2}:\d{2}(\.\d{1,9})?(Z|[+-]\d{2}:\d{2})?)?`)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package toml
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -22,6 +23,7 @@ const (
|
|||
|
||||
type tomlOpts struct {
|
||||
name string
|
||||
nameFromTag bool
|
||||
comment string
|
||||
commented bool
|
||||
multiline bool
|
||||
|
@ -68,9 +70,13 @@ const (
|
|||
|
||||
var timeType = reflect.TypeOf(time.Time{})
|
||||
var marshalerType = reflect.TypeOf(new(Marshaler)).Elem()
|
||||
var unmarshalerType = reflect.TypeOf(new(Unmarshaler)).Elem()
|
||||
var textMarshalerType = reflect.TypeOf(new(encoding.TextMarshaler)).Elem()
|
||||
var textUnmarshalerType = reflect.TypeOf(new(encoding.TextUnmarshaler)).Elem()
|
||||
var localDateType = reflect.TypeOf(LocalDate{})
|
||||
var localTimeType = reflect.TypeOf(LocalTime{})
|
||||
var localDateTimeType = reflect.TypeOf(LocalDateTime{})
|
||||
var mapStringInterfaceType = reflect.TypeOf(map[string]interface{}{})
|
||||
|
||||
// Check if the given marshal type maps to a Tree primitive
|
||||
func isPrimitive(mtype reflect.Type) bool {
|
||||
|
@ -88,12 +94,16 @@ func isPrimitive(mtype reflect.Type) bool {
|
|||
case reflect.String:
|
||||
return true
|
||||
case reflect.Struct:
|
||||
return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || mtype == localTimeType || isCustomMarshaler(mtype)
|
||||
return isTimeType(mtype)
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isTimeType(mtype reflect.Type) bool {
|
||||
return mtype == timeType || mtype == localDateType || mtype == localDateTimeType || mtype == localTimeType
|
||||
}
|
||||
|
||||
// Check if the given marshal type maps to a Tree slice or array
|
||||
func isTreeSequence(mtype reflect.Type) bool {
|
||||
switch mtype.Kind() {
|
||||
|
@ -106,6 +116,30 @@ func isTreeSequence(mtype reflect.Type) bool {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if the given marshal type maps to a slice or array of a custom marshaler type
|
||||
func isCustomMarshalerSequence(mtype reflect.Type) bool {
|
||||
switch mtype.Kind() {
|
||||
case reflect.Ptr:
|
||||
return isCustomMarshalerSequence(mtype.Elem())
|
||||
case reflect.Slice, reflect.Array:
|
||||
return isCustomMarshaler(mtype.Elem()) || isCustomMarshaler(reflect.New(mtype.Elem()).Type())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the given marshal type maps to a slice or array of a text marshaler type
|
||||
func isTextMarshalerSequence(mtype reflect.Type) bool {
|
||||
switch mtype.Kind() {
|
||||
case reflect.Ptr:
|
||||
return isTextMarshalerSequence(mtype.Elem())
|
||||
case reflect.Slice, reflect.Array:
|
||||
return isTextMarshaler(mtype.Elem()) || isTextMarshaler(reflect.New(mtype.Elem()).Type())
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the given marshal type maps to a non-Tree slice or array
|
||||
func isOtherSequence(mtype reflect.Type) bool {
|
||||
switch mtype.Kind() {
|
||||
|
@ -140,12 +174,42 @@ func callCustomMarshaler(mval reflect.Value) ([]byte, error) {
|
|||
return mval.Interface().(Marshaler).MarshalTOML()
|
||||
}
|
||||
|
||||
func isTextMarshaler(mtype reflect.Type) bool {
|
||||
return mtype.Implements(textMarshalerType) && !isTimeType(mtype)
|
||||
}
|
||||
|
||||
func callTextMarshaler(mval reflect.Value) ([]byte, error) {
|
||||
return mval.Interface().(encoding.TextMarshaler).MarshalText()
|
||||
}
|
||||
|
||||
func isCustomUnmarshaler(mtype reflect.Type) bool {
|
||||
return mtype.Implements(unmarshalerType)
|
||||
}
|
||||
|
||||
func callCustomUnmarshaler(mval reflect.Value, tval interface{}) error {
|
||||
return mval.Interface().(Unmarshaler).UnmarshalTOML(tval)
|
||||
}
|
||||
|
||||
func isTextUnmarshaler(mtype reflect.Type) bool {
|
||||
return mtype.Implements(textUnmarshalerType)
|
||||
}
|
||||
|
||||
func callTextUnmarshaler(mval reflect.Value, text []byte) error {
|
||||
return mval.Interface().(encoding.TextUnmarshaler).UnmarshalText(text)
|
||||
}
|
||||
|
||||
// Marshaler is the interface implemented by types that
|
||||
// can marshal themselves into valid TOML.
|
||||
type Marshaler interface {
|
||||
MarshalTOML() ([]byte, error)
|
||||
}
|
||||
|
||||
// Unmarshaler is the interface implemented by types that
|
||||
// can unmarshal a TOML description of themselves.
|
||||
type Unmarshaler interface {
|
||||
UnmarshalTOML(interface{}) error
|
||||
}
|
||||
|
||||
/*
|
||||
Marshal returns the TOML encoding of v. Behavior is similar to the Go json
|
||||
encoder, except that there is no concept of a Marshaler interface or MarshalTOML
|
||||
|
@ -190,20 +254,23 @@ type Encoder struct {
|
|||
w io.Writer
|
||||
encOpts
|
||||
annotation
|
||||
line int
|
||||
col int
|
||||
order marshalOrder
|
||||
line int
|
||||
col int
|
||||
order marshalOrder
|
||||
promoteAnon bool
|
||||
indentation string
|
||||
}
|
||||
|
||||
// NewEncoder returns a new encoder that writes to w.
|
||||
func NewEncoder(w io.Writer) *Encoder {
|
||||
return &Encoder{
|
||||
w: w,
|
||||
encOpts: encOptsDefaults,
|
||||
annotation: annotationDefault,
|
||||
line: 0,
|
||||
col: 1,
|
||||
order: OrderAlphabetical,
|
||||
w: w,
|
||||
encOpts: encOptsDefaults,
|
||||
annotation: annotationDefault,
|
||||
line: 0,
|
||||
col: 1,
|
||||
order: OrderAlphabetical,
|
||||
indentation: " ",
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -255,6 +322,12 @@ func (e *Encoder) Order(ord marshalOrder) *Encoder {
|
|||
return e
|
||||
}
|
||||
|
||||
// Indentation allows to change indentation when marshalling.
|
||||
func (e *Encoder) Indentation(indent string) *Encoder {
|
||||
e.indentation = indent
|
||||
return e
|
||||
}
|
||||
|
||||
// SetTagName allows changing default tag "toml"
|
||||
func (e *Encoder) SetTagName(v string) *Encoder {
|
||||
e.tag = v
|
||||
|
@ -279,8 +352,31 @@ func (e *Encoder) SetTagMultiline(v string) *Encoder {
|
|||
return e
|
||||
}
|
||||
|
||||
// PromoteAnonymous allows to change how anonymous struct fields are marshaled.
|
||||
// Usually, they are marshaled as if the inner exported fields were fields in
|
||||
// the outer struct. However, if an anonymous struct field is given a name in
|
||||
// its TOML tag, it is treated like a regular struct field with that name.
|
||||
// rather than being anonymous.
|
||||
//
|
||||
// In case anonymous promotion is enabled, all anonymous structs are promoted
|
||||
// and treated like regular struct fields.
|
||||
func (e *Encoder) PromoteAnonymous(promote bool) *Encoder {
|
||||
e.promoteAnon = promote
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *Encoder) marshal(v interface{}) ([]byte, error) {
|
||||
// Check if indentation is valid
|
||||
for _, char := range e.indentation {
|
||||
if !isSpace(char) {
|
||||
return []byte{}, fmt.Errorf("invalid indentation: must only contains space or tab characters")
|
||||
}
|
||||
}
|
||||
|
||||
mtype := reflect.TypeOf(v)
|
||||
if mtype == nil {
|
||||
return []byte{}, errors.New("nil cannot be marshaled to TOML")
|
||||
}
|
||||
|
||||
switch mtype.Kind() {
|
||||
case reflect.Struct, reflect.Map:
|
||||
|
@ -288,6 +384,9 @@ func (e *Encoder) marshal(v interface{}) ([]byte, error) {
|
|||
if mtype.Elem().Kind() != reflect.Struct {
|
||||
return []byte{}, errors.New("Only pointer to struct can be marshaled to TOML")
|
||||
}
|
||||
if reflect.ValueOf(v).IsNil() {
|
||||
return []byte{}, errors.New("nil pointer cannot be marshaled to TOML")
|
||||
}
|
||||
default:
|
||||
return []byte{}, errors.New("Only a struct or map can be marshaled to TOML")
|
||||
}
|
||||
|
@ -296,13 +395,16 @@ func (e *Encoder) marshal(v interface{}) ([]byte, error) {
|
|||
if isCustomMarshaler(mtype) {
|
||||
return callCustomMarshaler(sval)
|
||||
}
|
||||
if isTextMarshaler(mtype) {
|
||||
return callTextMarshaler(sval)
|
||||
}
|
||||
t, err := e.valueToTree(mtype, sval)
|
||||
if err != nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, false)
|
||||
_, err = t.writeToOrdered(&buf, "", "", 0, e.arraysOneElementPerLine, e.order, e.indentation, false)
|
||||
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
@ -332,12 +434,16 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tval.SetWithOptions(opts.name, SetOptions{
|
||||
Comment: opts.comment,
|
||||
Commented: opts.commented,
|
||||
Multiline: opts.multiline,
|
||||
}, val)
|
||||
if tree, ok := val.(*Tree); ok && mtypef.Anonymous && !opts.nameFromTag && !e.promoteAnon {
|
||||
e.appendTree(tval, tree)
|
||||
} else {
|
||||
val = e.wrapTomlValue(val, tval)
|
||||
tval.SetPathWithOptions([]string{opts.name}, SetOptions{
|
||||
Comment: opts.comment,
|
||||
Commented: opts.commented,
|
||||
Multiline: opts.multiline,
|
||||
}, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -370,14 +476,15 @@ func (e *Encoder) valueToTree(mtype reflect.Type, mval reflect.Value) (*Tree, er
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
val = e.wrapTomlValue(val, tval)
|
||||
if e.quoteMapKeys {
|
||||
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.arraysOneElementPerLine)
|
||||
keyStr, err := tomlValueStringRepresentation(key.String(), "", "", e.order, e.arraysOneElementPerLine)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tval.SetPath([]string{keyStr}, val)
|
||||
} else {
|
||||
tval.Set(key.String(), val)
|
||||
tval.SetPath([]string{key.String()}, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -399,9 +506,6 @@ func (e *Encoder) valueToTreeSlice(mtype reflect.Type, mval reflect.Value) ([]*T
|
|||
|
||||
// Convert given marshal slice to slice of toml values
|
||||
func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
|
||||
if mtype.Elem().Kind() == reflect.Interface {
|
||||
return nil, fmt.Errorf("marshal can't handle []interface{}")
|
||||
}
|
||||
tval := make([]interface{}, mval.Len(), mval.Len())
|
||||
for i := 0; i < mval.Len(); i++ {
|
||||
val, err := e.valueToToml(mtype.Elem(), mval.Index(i))
|
||||
|
@ -415,9 +519,16 @@ func (e *Encoder) valueToOtherSlice(mtype reflect.Type, mval reflect.Value) (int
|
|||
|
||||
// Convert given marshal value to toml value
|
||||
func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface{}, error) {
|
||||
e.line++
|
||||
if mtype.Kind() == reflect.Ptr {
|
||||
return e.valueToToml(mtype.Elem(), mval.Elem())
|
||||
switch {
|
||||
case isCustomMarshaler(mtype):
|
||||
return callCustomMarshaler(mval)
|
||||
case isTextMarshaler(mtype):
|
||||
b, err := callTextMarshaler(mval)
|
||||
return string(b), err
|
||||
default:
|
||||
return e.valueToToml(mtype.Elem(), mval.Elem())
|
||||
}
|
||||
}
|
||||
if mtype.Kind() == reflect.Interface {
|
||||
return e.valueToToml(mval.Elem().Type(), mval.Elem())
|
||||
|
@ -425,12 +536,15 @@ func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface
|
|||
switch {
|
||||
case isCustomMarshaler(mtype):
|
||||
return callCustomMarshaler(mval)
|
||||
case isTextMarshaler(mtype):
|
||||
b, err := callTextMarshaler(mval)
|
||||
return string(b), err
|
||||
case isTree(mtype):
|
||||
return e.valueToTree(mtype, mval)
|
||||
case isOtherSequence(mtype), isCustomMarshalerSequence(mtype), isTextMarshalerSequence(mtype):
|
||||
return e.valueToOtherSlice(mtype, mval)
|
||||
case isTreeSequence(mtype):
|
||||
return e.valueToTreeSlice(mtype, mval)
|
||||
case isOtherSequence(mtype):
|
||||
return e.valueToOtherSlice(mtype, mval)
|
||||
default:
|
||||
switch mtype.Kind() {
|
||||
case reflect.Bool:
|
||||
|
@ -454,6 +568,38 @@ func (e *Encoder) valueToToml(mtype reflect.Type, mval reflect.Value) (interface
|
|||
}
|
||||
}
|
||||
|
||||
func (e *Encoder) appendTree(t, o *Tree) error {
|
||||
for key, value := range o.values {
|
||||
if _, ok := t.values[key]; ok {
|
||||
continue
|
||||
}
|
||||
if tomlValue, ok := value.(*tomlValue); ok {
|
||||
tomlValue.position.Col = t.position.Col
|
||||
}
|
||||
t.values[key] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a toml value with the current line number as the position line
|
||||
func (e *Encoder) wrapTomlValue(val interface{}, parent *Tree) interface{} {
|
||||
_, isTree := val.(*Tree)
|
||||
_, isTreeS := val.([]*Tree)
|
||||
if isTree || isTreeS {
|
||||
return val
|
||||
}
|
||||
|
||||
ret := &tomlValue{
|
||||
value: val,
|
||||
position: Position{
|
||||
e.line,
|
||||
parent.position.Col,
|
||||
},
|
||||
}
|
||||
e.line++
|
||||
return ret
|
||||
}
|
||||
|
||||
// Unmarshal attempts to unmarshal the Tree into a Go struct pointed by v.
|
||||
// Neither Unmarshaler interfaces nor UnmarshalTOML functions are supported for
|
||||
// sub-structs, and only definite types can be unmarshaled.
|
||||
|
@ -506,6 +652,8 @@ type Decoder struct {
|
|||
tval *Tree
|
||||
encOpts
|
||||
tagName string
|
||||
strict bool
|
||||
visitor visitorState
|
||||
}
|
||||
|
||||
// NewDecoder returns a new decoder that reads from r.
|
||||
|
@ -536,8 +684,18 @@ func (d *Decoder) SetTagName(v string) *Decoder {
|
|||
return d
|
||||
}
|
||||
|
||||
// Strict allows changing to strict decoding. Any fields that are found in the
|
||||
// input data and do not have a corresponding struct member cause an error.
|
||||
func (d *Decoder) Strict(strict bool) *Decoder {
|
||||
d.strict = strict
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Decoder) unmarshal(v interface{}) error {
|
||||
mtype := reflect.TypeOf(v)
|
||||
if mtype == nil {
|
||||
return errors.New("nil cannot be unmarshaled from TOML")
|
||||
}
|
||||
if mtype.Kind() != reflect.Ptr {
|
||||
return errors.New("only a pointer to struct or map can be unmarshaled from TOML")
|
||||
}
|
||||
|
@ -546,16 +704,29 @@ func (d *Decoder) unmarshal(v interface{}) error {
|
|||
|
||||
switch elem.Kind() {
|
||||
case reflect.Struct, reflect.Map:
|
||||
case reflect.Interface:
|
||||
elem = mapStringInterfaceType
|
||||
default:
|
||||
return errors.New("only a pointer to struct or map can be unmarshaled from TOML")
|
||||
}
|
||||
|
||||
if reflect.ValueOf(v).IsNil() {
|
||||
return errors.New("nil pointer cannot be unmarshaled from TOML")
|
||||
}
|
||||
|
||||
vv := reflect.ValueOf(v).Elem()
|
||||
|
||||
if d.strict {
|
||||
d.visitor = newVisitorState(d.tval)
|
||||
}
|
||||
|
||||
sval, err := d.valueFromTree(elem, d.tval, &vv)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.visitor.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
reflect.ValueOf(v).Elem().Set(sval)
|
||||
return nil
|
||||
}
|
||||
|
@ -566,6 +737,21 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
if mtype.Kind() == reflect.Ptr {
|
||||
return d.unwrapPointer(mtype, tval, mval1)
|
||||
}
|
||||
|
||||
// Check if pointer to value implements the Unmarshaler interface.
|
||||
if mvalPtr := reflect.New(mtype); isCustomUnmarshaler(mvalPtr.Type()) {
|
||||
d.visitor.visitAll()
|
||||
|
||||
if tval == nil {
|
||||
return mvalPtr.Elem(), nil
|
||||
}
|
||||
|
||||
if err := callCustomUnmarshaler(mvalPtr, tval.ToMap()); err != nil {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("unmarshal toml: %v", err)
|
||||
}
|
||||
return mvalPtr.Elem(), nil
|
||||
}
|
||||
|
||||
var mval reflect.Value
|
||||
switch mtype.Kind() {
|
||||
case reflect.Struct:
|
||||
|
@ -597,18 +783,21 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
found := false
|
||||
if tval != nil {
|
||||
for _, key := range keysToTry {
|
||||
exists := tval.Has(key)
|
||||
exists := tval.HasPath([]string{key})
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
val := tval.Get(key)
|
||||
|
||||
d.visitor.push(key)
|
||||
val := tval.GetPath([]string{key})
|
||||
fval := mval.Field(i)
|
||||
mvalf, err := d.valueFromToml(mtypef.Type, val, &fval)
|
||||
if err != nil {
|
||||
return mval, formatError(err, tval.GetPosition(key))
|
||||
return mval, formatError(err, tval.GetPositionPath([]string{key}))
|
||||
}
|
||||
mval.Field(i).Set(mvalf)
|
||||
found = true
|
||||
d.visitor.pop()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -618,32 +807,42 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
var val interface{}
|
||||
var err error
|
||||
switch mvalf.Kind() {
|
||||
case reflect.Bool:
|
||||
val, err = strconv.ParseBool(opts.defaultValue)
|
||||
if err != nil {
|
||||
return mval.Field(i), err
|
||||
}
|
||||
case reflect.Int:
|
||||
val, err = strconv.Atoi(opts.defaultValue)
|
||||
if err != nil {
|
||||
return mval.Field(i), err
|
||||
}
|
||||
case reflect.String:
|
||||
val = opts.defaultValue
|
||||
case reflect.Bool:
|
||||
val, err = strconv.ParseBool(opts.defaultValue)
|
||||
case reflect.Uint:
|
||||
val, err = strconv.ParseUint(opts.defaultValue, 10, 0)
|
||||
case reflect.Uint8:
|
||||
val, err = strconv.ParseUint(opts.defaultValue, 10, 8)
|
||||
case reflect.Uint16:
|
||||
val, err = strconv.ParseUint(opts.defaultValue, 10, 16)
|
||||
case reflect.Uint32:
|
||||
val, err = strconv.ParseUint(opts.defaultValue, 10, 32)
|
||||
case reflect.Uint64:
|
||||
val, err = strconv.ParseUint(opts.defaultValue, 10, 64)
|
||||
case reflect.Int:
|
||||
val, err = strconv.ParseInt(opts.defaultValue, 10, 0)
|
||||
case reflect.Int8:
|
||||
val, err = strconv.ParseInt(opts.defaultValue, 10, 8)
|
||||
case reflect.Int16:
|
||||
val, err = strconv.ParseInt(opts.defaultValue, 10, 16)
|
||||
case reflect.Int32:
|
||||
val, err = strconv.ParseInt(opts.defaultValue, 10, 32)
|
||||
case reflect.Int64:
|
||||
val, err = strconv.ParseInt(opts.defaultValue, 10, 64)
|
||||
if err != nil {
|
||||
return mval.Field(i), err
|
||||
}
|
||||
case reflect.Float32:
|
||||
val, err = strconv.ParseFloat(opts.defaultValue, 32)
|
||||
case reflect.Float64:
|
||||
val, err = strconv.ParseFloat(opts.defaultValue, 64)
|
||||
if err != nil {
|
||||
return mval.Field(i), err
|
||||
}
|
||||
default:
|
||||
return mval.Field(i), fmt.Errorf("unsuported field type for default option")
|
||||
return mvalf, fmt.Errorf("unsupported field type for default option")
|
||||
}
|
||||
mval.Field(i).Set(reflect.ValueOf(val))
|
||||
|
||||
if err != nil {
|
||||
return mvalf, err
|
||||
}
|
||||
mvalf.Set(reflect.ValueOf(val).Convert(mvalf.Type()))
|
||||
}
|
||||
|
||||
// save the old behavior above and try to check structs
|
||||
|
@ -652,7 +851,8 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
if !mtypef.Anonymous {
|
||||
tmpTval = nil
|
||||
}
|
||||
v, err := d.valueFromTree(mtypef.Type, tmpTval, nil)
|
||||
fval := mval.Field(i)
|
||||
v, err := d.valueFromTree(mtypef.Type, tmpTval, &fval)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
@ -663,13 +863,15 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
case reflect.Map:
|
||||
mval = reflect.MakeMap(mtype)
|
||||
for _, key := range tval.Keys() {
|
||||
d.visitor.push(key)
|
||||
// TODO: path splits key
|
||||
val := tval.GetPath([]string{key})
|
||||
mvalf, err := d.valueFromToml(mtype.Elem(), val, nil)
|
||||
if err != nil {
|
||||
return mval, formatError(err, tval.GetPosition(key))
|
||||
return mval, formatError(err, tval.GetPositionPath([]string{key}))
|
||||
}
|
||||
mval.SetMapIndex(reflect.ValueOf(key).Convert(mtype.Key()), mvalf)
|
||||
d.visitor.pop()
|
||||
}
|
||||
}
|
||||
return mval, nil
|
||||
|
@ -677,22 +879,52 @@ func (d *Decoder) valueFromTree(mtype reflect.Type, tval *Tree, mval1 *reflect.V
|
|||
|
||||
// Convert toml value to marshal struct/map slice, using marshal type
|
||||
func (d *Decoder) valueFromTreeSlice(mtype reflect.Type, tval []*Tree) (reflect.Value, error) {
|
||||
mval := reflect.MakeSlice(mtype, len(tval), len(tval))
|
||||
mval, err := makeSliceOrArray(mtype, len(tval))
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(tval); i++ {
|
||||
d.visitor.push(strconv.Itoa(i))
|
||||
val, err := d.valueFromTree(mtype.Elem(), tval[i], nil)
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
mval.Index(i).Set(val)
|
||||
d.visitor.pop()
|
||||
}
|
||||
return mval, nil
|
||||
}
|
||||
|
||||
// Convert toml value to marshal primitive slice, using marshal type
|
||||
func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) {
|
||||
mval, err := makeSliceOrArray(mtype, len(tval))
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(tval); i++ {
|
||||
val, err := d.valueFromToml(mtype.Elem(), tval[i], nil)
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
mval.Index(i).Set(val)
|
||||
}
|
||||
return mval, nil
|
||||
}
|
||||
|
||||
// Convert toml value to marshal primitive slice, using marshal type
|
||||
func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (reflect.Value, error) {
|
||||
mval := reflect.MakeSlice(mtype, len(tval), len(tval))
|
||||
for i := 0; i < len(tval); i++ {
|
||||
val, err := d.valueFromToml(mtype.Elem(), tval[i], nil)
|
||||
func (d *Decoder) valueFromOtherSliceI(mtype reflect.Type, tval interface{}) (reflect.Value, error) {
|
||||
val := reflect.ValueOf(tval)
|
||||
length := val.Len()
|
||||
|
||||
mval, err := makeSliceOrArray(mtype, length)
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
val, err := d.valueFromToml(mtype.Elem(), val.Index(i).Interface(), nil)
|
||||
if err != nil {
|
||||
return mval, err
|
||||
}
|
||||
|
@ -701,6 +933,21 @@ func (d *Decoder) valueFromOtherSlice(mtype reflect.Type, tval []interface{}) (r
|
|||
return mval, nil
|
||||
}
|
||||
|
||||
// Create a new slice or a new array with specified length
|
||||
func makeSliceOrArray(mtype reflect.Type, tLength int) (reflect.Value, error) {
|
||||
var mval reflect.Value
|
||||
switch mtype.Kind() {
|
||||
case reflect.Slice:
|
||||
mval = reflect.MakeSlice(mtype, tLength, tLength)
|
||||
case reflect.Array:
|
||||
mval = reflect.New(reflect.ArrayOf(mtype.Len(), mtype.Elem())).Elem()
|
||||
if tLength > mtype.Len() {
|
||||
return mval, fmt.Errorf("unmarshal: TOML array length (%v) exceeds destination array length (%v)", tLength, mtype.Len())
|
||||
}
|
||||
}
|
||||
return mval, nil
|
||||
}
|
||||
|
||||
// Convert toml value to marshal value, using marshal type. When mval1 is non-nil
|
||||
// and the given type is a struct value, merge fields into it.
|
||||
func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *reflect.Value) (reflect.Value, error) {
|
||||
|
@ -742,6 +989,7 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
|
|||
}
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to trees", tval, tval)
|
||||
case []interface{}:
|
||||
d.visitor.visit()
|
||||
if isOtherSequence(mtype) {
|
||||
return d.valueFromOtherSlice(mtype, t)
|
||||
}
|
||||
|
@ -755,6 +1003,15 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
|
|||
}
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to a slice", tval, tval)
|
||||
default:
|
||||
d.visitor.visit()
|
||||
// Check if pointer to value implements the encoding.TextUnmarshaler.
|
||||
if mvalPtr := reflect.New(mtype); isTextUnmarshaler(mvalPtr.Type()) && !isTimeType(mtype) {
|
||||
if err := d.unmarshalText(tval, mvalPtr); err != nil {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("unmarshal text: %v", err)
|
||||
}
|
||||
return mvalPtr.Elem(), nil
|
||||
}
|
||||
|
||||
switch mtype.Kind() {
|
||||
case reflect.Bool, reflect.Struct:
|
||||
val := reflect.ValueOf(tval)
|
||||
|
@ -805,34 +1062,34 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
|
|||
}
|
||||
return reflect.ValueOf(d), nil
|
||||
}
|
||||
if !val.Type().ConvertibleTo(mtype) {
|
||||
if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String())
|
||||
}
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Convert(mtype).Int()) {
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowInt(val.Convert(reflect.TypeOf(int64(0))).Int()) {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String())
|
||||
}
|
||||
|
||||
return val.Convert(mtype), nil
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
val := reflect.ValueOf(tval)
|
||||
if !val.Type().ConvertibleTo(mtype) {
|
||||
if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Float64 {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String())
|
||||
}
|
||||
|
||||
if val.Convert(reflect.TypeOf(int(1))).Int() < 0 {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("%v(%T) is negative so does not fit in %v", tval, tval, mtype.String())
|
||||
}
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowUint(uint64(val.Convert(mtype).Uint())) {
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowUint(val.Convert(reflect.TypeOf(uint64(0))).Uint()) {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String())
|
||||
}
|
||||
|
||||
return val.Convert(mtype), nil
|
||||
case reflect.Float32, reflect.Float64:
|
||||
val := reflect.ValueOf(tval)
|
||||
if !val.Type().ConvertibleTo(mtype) {
|
||||
if !val.Type().ConvertibleTo(mtype) || val.Kind() == reflect.Int64 {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v", tval, tval, mtype.String())
|
||||
}
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Convert(mtype).Float()) {
|
||||
if reflect.Indirect(reflect.New(mtype)).OverflowFloat(val.Convert(reflect.TypeOf(float64(0))).Float()) {
|
||||
return reflect.ValueOf(nil), fmt.Errorf("%v(%T) would overflow %v", tval, tval, mtype.String())
|
||||
}
|
||||
|
||||
|
@ -844,6 +1101,11 @@ func (d *Decoder) valueFromToml(mtype reflect.Type, tval interface{}, mval1 *ref
|
|||
ival := mval1.Elem()
|
||||
return d.valueFromToml(mval1.Elem().Type(), t, &ival)
|
||||
}
|
||||
case reflect.Slice, reflect.Array:
|
||||
if isOtherSequence(mtype) && isOtherSequence(reflect.TypeOf(t)) {
|
||||
return d.valueFromOtherSliceI(mtype, t)
|
||||
}
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind())
|
||||
default:
|
||||
return reflect.ValueOf(nil), fmt.Errorf("Can't convert %v(%T) to %v(%v)", tval, tval, mtype, mtype.Kind())
|
||||
}
|
||||
|
@ -867,6 +1129,12 @@ func (d *Decoder) unwrapPointer(mtype reflect.Type, tval interface{}, mval1 *ref
|
|||
return mval, nil
|
||||
}
|
||||
|
||||
func (d *Decoder) unmarshalText(tval interface{}, mval reflect.Value) error {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprint(&buf, tval)
|
||||
return callTextUnmarshaler(mval, buf.Bytes())
|
||||
}
|
||||
|
||||
func tomlOptions(vf reflect.StructField, an annotation) tomlOpts {
|
||||
tag := vf.Tag.Get(an.tag)
|
||||
parse := strings.Split(tag, ",")
|
||||
|
@ -879,6 +1147,7 @@ func tomlOptions(vf reflect.StructField, an annotation) tomlOpts {
|
|||
defaultValue := vf.Tag.Get(tagDefault)
|
||||
result := tomlOpts{
|
||||
name: vf.Name,
|
||||
nameFromTag: false,
|
||||
comment: comment,
|
||||
commented: commented,
|
||||
multiline: multiline,
|
||||
|
@ -891,6 +1160,7 @@ func tomlOptions(vf reflect.StructField, an annotation) tomlOpts {
|
|||
result.include = false
|
||||
} else {
|
||||
result.name = strings.Trim(parse[0], " ")
|
||||
result.nameFromTag = true
|
||||
}
|
||||
}
|
||||
if vf.PkgPath != "" {
|
||||
|
@ -907,11 +1177,7 @@ func tomlOptions(vf reflect.StructField, an annotation) tomlOpts {
|
|||
|
||||
func isZero(val reflect.Value) bool {
|
||||
switch val.Type().Kind() {
|
||||
case reflect.Map:
|
||||
fallthrough
|
||||
case reflect.Array:
|
||||
fallthrough
|
||||
case reflect.Slice:
|
||||
case reflect.Slice, reflect.Array, reflect.Map:
|
||||
return val.Len() == 0
|
||||
default:
|
||||
return reflect.DeepEqual(val.Interface(), reflect.Zero(val.Type()).Interface())
|
||||
|
@ -924,3 +1190,80 @@ func formatError(err error, pos Position) error {
|
|||
}
|
||||
return fmt.Errorf("%s: %s", pos, err)
|
||||
}
|
||||
|
||||
// visitorState keeps track of which keys were unmarshaled.
|
||||
type visitorState struct {
|
||||
tree *Tree
|
||||
path []string
|
||||
keys map[string]struct{}
|
||||
active bool
|
||||
}
|
||||
|
||||
func newVisitorState(tree *Tree) visitorState {
|
||||
path, result := []string{}, map[string]struct{}{}
|
||||
insertKeys(path, result, tree)
|
||||
return visitorState{
|
||||
tree: tree,
|
||||
path: path[:0],
|
||||
keys: result,
|
||||
active: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *visitorState) push(key string) {
|
||||
if s.active {
|
||||
s.path = append(s.path, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *visitorState) pop() {
|
||||
if s.active {
|
||||
s.path = s.path[:len(s.path)-1]
|
||||
}
|
||||
}
|
||||
|
||||
func (s *visitorState) visit() {
|
||||
if s.active {
|
||||
delete(s.keys, strings.Join(s.path, "."))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *visitorState) visitAll() {
|
||||
if s.active {
|
||||
for k := range s.keys {
|
||||
if strings.HasPrefix(k, strings.Join(s.path, ".")) {
|
||||
delete(s.keys, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *visitorState) validate() error {
|
||||
if !s.active {
|
||||
return nil
|
||||
}
|
||||
undecoded := make([]string, 0, len(s.keys))
|
||||
for key := range s.keys {
|
||||
undecoded = append(undecoded, key)
|
||||
}
|
||||
sort.Strings(undecoded)
|
||||
if len(undecoded) > 0 {
|
||||
return fmt.Errorf("undecoded keys: %q", undecoded)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func insertKeys(path []string, m map[string]struct{}, tree *Tree) {
|
||||
for k, v := range tree.values {
|
||||
switch node := v.(type) {
|
||||
case []*Tree:
|
||||
for i, item := range node {
|
||||
insertKeys(append(path, k, strconv.Itoa(i)), m, item)
|
||||
}
|
||||
case *Tree:
|
||||
insertKeys(append(path, k), m, node)
|
||||
case *tomlValue:
|
||||
m[strings.Join(append(path, k), ".")] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -158,6 +158,11 @@ func (p *tomlParser) parseGroup() tomlParserStateFn {
|
|||
if err := p.tree.createSubTree(keys, startToken.Position); err != nil {
|
||||
p.raiseError(key, "%s", err)
|
||||
}
|
||||
destTree := p.tree.GetPath(keys)
|
||||
if target, ok := destTree.(*Tree); ok && target != nil && target.inline {
|
||||
p.raiseError(key, "could not re-define exist inline table or its sub-table : %s",
|
||||
strings.Join(keys, "."))
|
||||
}
|
||||
p.assume(tokenRightBracket)
|
||||
p.currentTable = keys
|
||||
return p.parseStart
|
||||
|
@ -201,6 +206,11 @@ func (p *tomlParser) parseAssign() tomlParserStateFn {
|
|||
strings.Join(tableKey, "."))
|
||||
}
|
||||
|
||||
if targetNode.inline {
|
||||
p.raiseError(key, "could not add key or sub-table to exist inline table or its sub-table : %s",
|
||||
strings.Join(tableKey, "."))
|
||||
}
|
||||
|
||||
// assign value to the found table
|
||||
keyVal := parsedKey[len(parsedKey)-1]
|
||||
localKey := []string{keyVal}
|
||||
|
@ -411,12 +421,13 @@ Loop:
|
|||
if tokenIsComma(previous) {
|
||||
p.raiseError(previous, "trailing comma at the end of inline table")
|
||||
}
|
||||
tree.inline = true
|
||||
return tree
|
||||
}
|
||||
|
||||
func (p *tomlParser) parseArray() interface{} {
|
||||
var array []interface{}
|
||||
arrayType := reflect.TypeOf(nil)
|
||||
arrayType := reflect.TypeOf(newTree())
|
||||
for {
|
||||
follow := p.peek()
|
||||
if follow == nil || follow.typ == tokenEOF {
|
||||
|
@ -427,11 +438,8 @@ func (p *tomlParser) parseArray() interface{} {
|
|||
break
|
||||
}
|
||||
val := p.parseRvalue()
|
||||
if arrayType == nil {
|
||||
arrayType = reflect.TypeOf(val)
|
||||
}
|
||||
if reflect.TypeOf(val) != arrayType {
|
||||
p.raiseError(follow, "mixed types in array")
|
||||
arrayType = nil
|
||||
}
|
||||
array = append(array, val)
|
||||
follow = p.peek()
|
||||
|
@ -445,6 +453,12 @@ func (p *tomlParser) parseArray() interface{} {
|
|||
p.getToken()
|
||||
}
|
||||
}
|
||||
|
||||
// if the array is a mixed-type array or its length is 0,
|
||||
// don't convert it to a table array
|
||||
if len(array) <= 0 {
|
||||
arrayType = nil
|
||||
}
|
||||
// An array of Trees is actually an array of inline
|
||||
// tables, which is a shorthand for a table array. If the
|
||||
// array was not converted from []interface{} to []*Tree,
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
package toml
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"unicode"
|
||||
)
|
||||
import "fmt"
|
||||
|
||||
// Define tokens
|
||||
type tokenType int
|
||||
|
@ -112,7 +109,7 @@ func isSpace(r rune) bool {
|
|||
}
|
||||
|
||||
func isAlphanumeric(r rune) bool {
|
||||
return unicode.IsLetter(r) || r == '_'
|
||||
return 'a' <= r && r <= 'z' || 'A' <= r && r <= 'Z' || r == '_'
|
||||
}
|
||||
|
||||
func isKeyChar(r rune) bool {
|
||||
|
@ -127,7 +124,7 @@ func isKeyStartChar(r rune) bool {
|
|||
}
|
||||
|
||||
func isDigit(r rune) bool {
|
||||
return unicode.IsNumber(r)
|
||||
return '0' <= r && r <= '9'
|
||||
}
|
||||
|
||||
func isHexDigit(r rune) bool {
|
||||
|
|
|
@ -23,6 +23,7 @@ type Tree struct {
|
|||
values map[string]interface{} // string -> *tomlValue, *Tree, []*Tree
|
||||
comment string
|
||||
commented bool
|
||||
inline bool
|
||||
position Position
|
||||
}
|
||||
|
||||
|
@ -121,6 +122,89 @@ func (t *Tree) GetPath(keys []string) interface{} {
|
|||
}
|
||||
}
|
||||
|
||||
// GetArray returns the value at key in the Tree.
|
||||
// It returns []string, []int64, etc type if key has homogeneous lists
|
||||
// Key is a dot-separated path (e.g. a.b.c) without single/double quoted strings.
|
||||
// Returns nil if the path does not exist in the tree.
|
||||
// If keys is of length zero, the current tree is returned.
|
||||
func (t *Tree) GetArray(key string) interface{} {
|
||||
if key == "" {
|
||||
return t
|
||||
}
|
||||
return t.GetArrayPath(strings.Split(key, "."))
|
||||
}
|
||||
|
||||
// GetArrayPath returns the element in the tree indicated by 'keys'.
|
||||
// If keys is of length zero, the current tree is returned.
|
||||
func (t *Tree) GetArrayPath(keys []string) interface{} {
|
||||
if len(keys) == 0 {
|
||||
return t
|
||||
}
|
||||
subtree := t
|
||||
for _, intermediateKey := range keys[:len(keys)-1] {
|
||||
value, exists := subtree.values[intermediateKey]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
switch node := value.(type) {
|
||||
case *Tree:
|
||||
subtree = node
|
||||
case []*Tree:
|
||||
// go to most recent element
|
||||
if len(node) == 0 {
|
||||
return nil
|
||||
}
|
||||
subtree = node[len(node)-1]
|
||||
default:
|
||||
return nil // cannot navigate through other node types
|
||||
}
|
||||
}
|
||||
// branch based on final node type
|
||||
switch node := subtree.values[keys[len(keys)-1]].(type) {
|
||||
case *tomlValue:
|
||||
switch n := node.value.(type) {
|
||||
case []interface{}:
|
||||
return getArray(n)
|
||||
default:
|
||||
return node.value
|
||||
}
|
||||
default:
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
// if homogeneous array, then return slice type object over []interface{}
|
||||
func getArray(n []interface{}) interface{} {
|
||||
var s []string
|
||||
var i64 []int64
|
||||
var f64 []float64
|
||||
var bl []bool
|
||||
for _, value := range n {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
s = append(s, v)
|
||||
case int64:
|
||||
i64 = append(i64, v)
|
||||
case float64:
|
||||
f64 = append(f64, v)
|
||||
case bool:
|
||||
bl = append(bl, v)
|
||||
default:
|
||||
return n
|
||||
}
|
||||
}
|
||||
if len(s) == len(n) {
|
||||
return s
|
||||
} else if len(i64) == len(n) {
|
||||
return i64
|
||||
} else if len(f64) == len(n) {
|
||||
return f64
|
||||
} else if len(bl) == len(n) {
|
||||
return bl
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// GetPosition returns the position of the given key.
|
||||
func (t *Tree) GetPosition(key string) Position {
|
||||
if key == "" {
|
||||
|
@ -129,6 +213,50 @@ func (t *Tree) GetPosition(key string) Position {
|
|||
return t.GetPositionPath(strings.Split(key, "."))
|
||||
}
|
||||
|
||||
// SetPositionPath sets the position of element in the tree indicated by 'keys'.
|
||||
// If keys is of length zero, the current tree position is set.
|
||||
func (t *Tree) SetPositionPath(keys []string, pos Position) {
|
||||
if len(keys) == 0 {
|
||||
t.position = pos
|
||||
return
|
||||
}
|
||||
subtree := t
|
||||
for _, intermediateKey := range keys[:len(keys)-1] {
|
||||
value, exists := subtree.values[intermediateKey]
|
||||
if !exists {
|
||||
return
|
||||
}
|
||||
switch node := value.(type) {
|
||||
case *Tree:
|
||||
subtree = node
|
||||
case []*Tree:
|
||||
// go to most recent element
|
||||
if len(node) == 0 {
|
||||
return
|
||||
}
|
||||
subtree = node[len(node)-1]
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
// branch based on final node type
|
||||
switch node := subtree.values[keys[len(keys)-1]].(type) {
|
||||
case *tomlValue:
|
||||
node.position = pos
|
||||
return
|
||||
case *Tree:
|
||||
node.position = pos
|
||||
return
|
||||
case []*Tree:
|
||||
// go to most recent element
|
||||
if len(node) == 0 {
|
||||
return
|
||||
}
|
||||
node[len(node)-1].position = pos
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// GetPositionPath returns the element in the tree indicated by 'keys'.
|
||||
// If keys is of length zero, the current tree is returned.
|
||||
func (t *Tree) GetPositionPath(keys []string) Position {
|
||||
|
@ -211,7 +339,8 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
|
|||
// go to most recent element
|
||||
if len(node) == 0 {
|
||||
// create element if it does not exist
|
||||
subtree.values[intermediateKey] = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
|
||||
node = append(node, newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col}))
|
||||
subtree.values[intermediateKey] = node
|
||||
}
|
||||
subtree = node[len(node)-1]
|
||||
}
|
||||
|
@ -231,6 +360,8 @@ func (t *Tree) SetPathWithOptions(keys []string, opts SetOptions, value interfac
|
|||
toInsert = value
|
||||
case *tomlValue:
|
||||
v.comment = opts.Comment
|
||||
v.commented = opts.Commented
|
||||
v.multiline = opts.Multiline
|
||||
toInsert = v
|
||||
default:
|
||||
toInsert = &tomlValue{value: value,
|
||||
|
@ -311,6 +442,7 @@ func (t *Tree) createSubTree(keys []string, pos Position) error {
|
|||
if !exists {
|
||||
tree := newTreeWithPosition(Position{Line: t.position.Line + i, Col: t.position.Col})
|
||||
tree.position = pos
|
||||
tree.inline = subtree.inline
|
||||
subtree.values[intermediateKey] = tree
|
||||
nextTree = tree
|
||||
}
|
||||
|
|
|
@ -57,6 +57,19 @@ func simpleValueCoercion(object interface{}) (interface{}, error) {
|
|||
return float64(original), nil
|
||||
case fmt.Stringer:
|
||||
return original.String(), nil
|
||||
case []interface{}:
|
||||
value := reflect.ValueOf(original)
|
||||
length := value.Len()
|
||||
arrayValue := reflect.MakeSlice(value.Type(), 0, length)
|
||||
for i := 0; i < length; i++ {
|
||||
val := value.Index(i).Interface()
|
||||
simpleValue, err := simpleValueCoercion(val)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arrayValue = reflect.Append(arrayValue, reflect.ValueOf(simpleValue))
|
||||
}
|
||||
return arrayValue.Interface(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("cannot convert type %T to Tree", object)
|
||||
}
|
||||
|
|
|
@ -30,9 +30,15 @@ type sortNode struct {
|
|||
// are preserved. Quotation marks and backslashes are also not escaped.
|
||||
func encodeMultilineTomlString(value string, commented string) string {
|
||||
var b bytes.Buffer
|
||||
adjacentQuoteCount := 0
|
||||
|
||||
b.WriteString(commented)
|
||||
for _, rr := range value {
|
||||
for i, rr := range value {
|
||||
if rr != '"' {
|
||||
adjacentQuoteCount = 0
|
||||
} else {
|
||||
adjacentQuoteCount++
|
||||
}
|
||||
switch rr {
|
||||
case '\b':
|
||||
b.WriteString(`\b`)
|
||||
|
@ -45,7 +51,12 @@ func encodeMultilineTomlString(value string, commented string) string {
|
|||
case '\r':
|
||||
b.WriteString("\r")
|
||||
case '"':
|
||||
b.WriteString(`"`)
|
||||
if adjacentQuoteCount >= 3 || i == len(value)-1 {
|
||||
adjacentQuoteCount = 0
|
||||
b.WriteString(`\"`)
|
||||
} else {
|
||||
b.WriteString(`"`)
|
||||
}
|
||||
case '\\':
|
||||
b.WriteString(`\`)
|
||||
default:
|
||||
|
@ -92,7 +103,30 @@ func encodeTomlString(value string) string {
|
|||
return b.String()
|
||||
}
|
||||
|
||||
func tomlValueStringRepresentation(v interface{}, commented string, indent string, arraysOneElementPerLine bool) (string, error) {
|
||||
func tomlTreeStringRepresentation(t *Tree, ord marshalOrder) (string, error) {
|
||||
var orderedVals []sortNode
|
||||
switch ord {
|
||||
case OrderPreserve:
|
||||
orderedVals = sortByLines(t)
|
||||
default:
|
||||
orderedVals = sortAlphabetical(t)
|
||||
}
|
||||
|
||||
var values []string
|
||||
for _, node := range orderedVals {
|
||||
k := node.key
|
||||
v := t.values[k]
|
||||
|
||||
repr, err := tomlValueStringRepresentation(v, "", "", ord, false)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
values = append(values, quoteKeyIfNeeded(k)+" = "+repr)
|
||||
}
|
||||
return "{ " + strings.Join(values, ", ") + " }", nil
|
||||
}
|
||||
|
||||
func tomlValueStringRepresentation(v interface{}, commented string, indent string, ord marshalOrder, arraysOneElementPerLine bool) (string, error) {
|
||||
// this interface check is added to dereference the change made in the writeTo function.
|
||||
// That change was made to allow this function to see formatting options.
|
||||
tv, ok := v.(*tomlValue)
|
||||
|
@ -129,7 +163,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
|
|||
return "\"" + encodeTomlString(value) + "\"", nil
|
||||
case []byte:
|
||||
b, _ := v.([]byte)
|
||||
return tomlValueStringRepresentation(string(b), commented, indent, arraysOneElementPerLine)
|
||||
return string(b), nil
|
||||
case bool:
|
||||
if value {
|
||||
return "true", nil
|
||||
|
@ -143,6 +177,8 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
|
|||
return value.String(), nil
|
||||
case LocalTime:
|
||||
return value.String(), nil
|
||||
case *Tree:
|
||||
return tomlTreeStringRepresentation(value, ord)
|
||||
case nil:
|
||||
return "", nil
|
||||
}
|
||||
|
@ -153,7 +189,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
|
|||
var values []string
|
||||
for i := 0; i < rv.Len(); i++ {
|
||||
item := rv.Index(i).Interface()
|
||||
itemRepr, err := tomlValueStringRepresentation(item, commented, indent, arraysOneElementPerLine)
|
||||
itemRepr, err := tomlValueStringRepresentation(item, commented, indent, ord, arraysOneElementPerLine)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -176,7 +212,7 @@ func tomlValueStringRepresentation(v interface{}, commented string, indent strin
|
|||
|
||||
return stringBuffer.String(), nil
|
||||
}
|
||||
return "[" + strings.Join(values, ",") + "]", nil
|
||||
return "[" + strings.Join(values, ", ") + "]", nil
|
||||
}
|
||||
return "", fmt.Errorf("unsupported value type %T: %v", v, v)
|
||||
}
|
||||
|
@ -271,10 +307,10 @@ func sortAlphabetical(t *Tree) (vals []sortNode) {
|
|||
}
|
||||
|
||||
func (t *Tree) writeTo(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool) (int64, error) {
|
||||
return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, false)
|
||||
return t.writeToOrdered(w, indent, keyspace, bytesCount, arraysOneElementPerLine, OrderAlphabetical, " ", false)
|
||||
}
|
||||
|
||||
func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, parentCommented bool) (int64, error) {
|
||||
func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount int64, arraysOneElementPerLine bool, ord marshalOrder, indentString string, parentCommented bool) (int64, error) {
|
||||
var orderedVals []sortNode
|
||||
|
||||
switch ord {
|
||||
|
@ -290,7 +326,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
|
|||
k := node.key
|
||||
v := t.values[k]
|
||||
|
||||
combinedKey := k
|
||||
combinedKey := quoteKeyIfNeeded(k)
|
||||
if keyspace != "" {
|
||||
combinedKey = keyspace + "." + combinedKey
|
||||
}
|
||||
|
@ -324,7 +360,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
|
|||
if err != nil {
|
||||
return bytesCount, err
|
||||
}
|
||||
bytesCount, err = node.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord, parentCommented || t.commented || tv.commented)
|
||||
bytesCount, err = node.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || tv.commented)
|
||||
if err != nil {
|
||||
return bytesCount, err
|
||||
}
|
||||
|
@ -340,7 +376,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
|
|||
return bytesCount, err
|
||||
}
|
||||
|
||||
bytesCount, err = subTree.writeToOrdered(w, indent+" ", combinedKey, bytesCount, arraysOneElementPerLine, ord, parentCommented || t.commented || subTree.commented)
|
||||
bytesCount, err = subTree.writeToOrdered(w, indent+indentString, combinedKey, bytesCount, arraysOneElementPerLine, ord, indentString, parentCommented || t.commented || subTree.commented)
|
||||
if err != nil {
|
||||
return bytesCount, err
|
||||
}
|
||||
|
@ -357,7 +393,7 @@ func (t *Tree) writeToOrdered(w io.Writer, indent, keyspace string, bytesCount i
|
|||
if parentCommented || t.commented || v.commented {
|
||||
commented = "# "
|
||||
}
|
||||
repr, err := tomlValueStringRepresentation(v, commented, indent, arraysOneElementPerLine)
|
||||
repr, err := tomlValueStringRepresentation(v, commented, indent, ord, arraysOneElementPerLine)
|
||||
if err != nil {
|
||||
return bytesCount, err
|
||||
}
|
||||
|
|
|
@ -573,6 +573,7 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
}
|
||||
i.autocompleteList.SetCurrentItem(newEntry)
|
||||
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
|
||||
currentText = stripTags(currentText)
|
||||
i.SetText(currentText)
|
||||
} else {
|
||||
finish(key)
|
||||
|
@ -585,6 +586,7 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
|
|||
}
|
||||
i.autocompleteList.SetCurrentItem(newEntry)
|
||||
currentText, _ = i.autocompleteList.GetItemText(newEntry) // Don't trigger changed function twice.
|
||||
currentText = stripTags(currentText)
|
||||
i.SetText(currentText)
|
||||
} else {
|
||||
finish(key)
|
||||
|
|
|
@ -42,6 +42,11 @@ type TableCell struct {
|
|||
// If set to true, this cell cannot be selected.
|
||||
NotSelectable bool
|
||||
|
||||
// An optional handler for mouse clicks. This also fires if the cell is not
|
||||
// selectable. If true is returned, no additional "selected" event is fired
|
||||
// on selectable cells.
|
||||
Clicked func() bool
|
||||
|
||||
// The position and width of the cell the last time table was drawn.
|
||||
x, y, width int
|
||||
}
|
||||
|
@ -160,6 +165,14 @@ func (c *TableCell) GetLastPosition() (x, y, width int) {
|
|||
return c.x, c.y, c.width
|
||||
}
|
||||
|
||||
// SetClickedFunc sets a handler which fires when this cell is clicked. This is
|
||||
// independent of whether the cell is selectable or not. But for selectable
|
||||
// cells, if the function returns "true", the "selected" event is not fired.
|
||||
func (c *TableCell) SetClickedFunc(clicked func() bool) *TableCell {
|
||||
c.Clicked = clicked
|
||||
return c
|
||||
}
|
||||
|
||||
// Table visualizes two-dimensional data consisting of rows and columns. Each
|
||||
// Table cell is defined via SetCell() by the TableCell type. They can be added
|
||||
// dynamically to the table and changed any time.
|
||||
|
@ -1247,8 +1260,21 @@ func (t *Table) MouseHandler() func(action MouseAction, event *tcell.EventMouse,
|
|||
|
||||
switch action {
|
||||
case MouseLeftClick:
|
||||
if t.rowsSelectable || t.columnsSelectable {
|
||||
t.Select(t.cellAt(x, y))
|
||||
selectEvent := true
|
||||
row, column := t.cellAt(x, y)
|
||||
if row >= 0 && row < len(t.cells) && column >= 0 {
|
||||
cells := t.cells[row]
|
||||
if column < len(cells) {
|
||||
cell := cells[column]
|
||||
if cell != nil && cell.Clicked != nil {
|
||||
if noSelect := cell.Clicked(); noSelect {
|
||||
selectEvent = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if selectEvent && (t.rowsSelectable || t.columnsSelectable) {
|
||||
t.Select(row, column)
|
||||
}
|
||||
setFocus(t)
|
||||
consumed = true
|
||||
|
|
|
@ -269,12 +269,13 @@ func (t *TextView) SetText(text string) *TextView {
|
|||
return t
|
||||
}
|
||||
|
||||
// GetText returns the current text of this text view. If "stripTags" is set
|
||||
// GetText returns the current text of this text view. If "stripAllTags" is set
|
||||
// to true, any region/color tags are stripped from the text.
|
||||
func (t *TextView) GetText(stripTags bool) string {
|
||||
func (t *TextView) GetText(stripAllTags bool) string {
|
||||
// Get the buffer.
|
||||
buffer := t.buffer
|
||||
if !stripTags {
|
||||
buffer := make([]string, len(t.buffer), len(t.buffer)+1)
|
||||
copy(buffer, t.buffer)
|
||||
if !stripAllTags {
|
||||
buffer = append(buffer, string(t.recentBytes))
|
||||
}
|
||||
|
||||
|
@ -282,19 +283,14 @@ func (t *TextView) GetText(stripTags bool) string {
|
|||
text := strings.Join(buffer, "\n")
|
||||
|
||||
// Strip from tags if required.
|
||||
if stripTags {
|
||||
if stripAllTags {
|
||||
if t.regions {
|
||||
text = regionPattern.ReplaceAllString(text, "")
|
||||
}
|
||||
if t.dynamicColors {
|
||||
text = colorPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
if len(match) > 2 {
|
||||
return ""
|
||||
}
|
||||
return match
|
||||
})
|
||||
text = stripTags(text)
|
||||
}
|
||||
if t.regions || t.dynamicColors {
|
||||
if t.regions && !t.dynamicColors {
|
||||
text = escapePattern.ReplaceAllString(text, `[$1$2]`)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -628,3 +628,15 @@ func iterateStringReverse(text string, callback func(main rune, comb []rune, tex
|
|||
|
||||
return false
|
||||
}
|
||||
|
||||
// stripTags strips colour tags from the given string. (Region tags are not
|
||||
// stripped.)
|
||||
func stripTags(text string) string {
|
||||
stripped := colorPattern.ReplaceAllStringFunc(text, func(match string) string {
|
||||
if len(match) > 2 {
|
||||
return ""
|
||||
}
|
||||
return match
|
||||
})
|
||||
return escapePattern.ReplaceAllString(stripped, `[$1$2]`)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright 2019 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 !gccgo
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
//
|
||||
// System call support for mips64, OpenBSD
|
||||
//
|
||||
|
||||
// Just jump to package syscall's implementation for all these functions.
|
||||
// The runtime may know about them.
|
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·Syscall(SB)
|
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·Syscall6(SB)
|
||||
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-104
|
||||
JMP syscall·Syscall9(SB)
|
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-56
|
||||
JMP syscall·RawSyscall(SB)
|
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-80
|
||||
JMP syscall·RawSyscall6(SB)
|
|
@ -16,3 +16,9 @@ func FcntlFlock(fd uintptr, cmd int, lk *Flock_t) error {
|
|||
_, err := fcntl(int(fd), cmd, int(uintptr(unsafe.Pointer(lk))))
|
||||
return err
|
||||
}
|
||||
|
||||
// FcntlFstore performs a fcntl syscall for the F_PREALLOCATE command.
|
||||
func FcntlFstore(fd uintptr, cmd int, fstore *Fstore_t) error {
|
||||
_, err := fcntl(int(fd), cmd, int(uintptr(unsafe.Pointer(fstore))))
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -20,6 +20,15 @@ func IoctlSetInt(fd int, req uint, value int) error {
|
|||
return ioctl(fd, req, uintptr(value))
|
||||
}
|
||||
|
||||
// IoctlSetPointerInt performs an ioctl operation which sets an
|
||||
// integer value on fd, using the specified request number. The ioctl
|
||||
// argument is called with a pointer to the integer value, rather than
|
||||
// passing the integer value directly.
|
||||
func IoctlSetPointerInt(fd int, req uint, value int) error {
|
||||
v := int32(value)
|
||||
return ioctl(fd, req, uintptr(unsafe.Pointer(&v)))
|
||||
}
|
||||
|
||||
// IoctlSetWinsize performs an ioctl on fd with a *Winsize argument.
|
||||
//
|
||||
// To change fd's window size, the req argument should be TIOCSWINSZ.
|
||||
|
|
|
@ -73,26 +73,22 @@ aix_ppc64)
|
|||
darwin_386)
|
||||
mkerrors="$mkerrors -m32"
|
||||
mksyscall="go run mksyscall.go -l32"
|
||||
mksysnum="go run mksysnum.go $(xcrun --show-sdk-path --sdk macosx)/usr/include/sys/syscall.h"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
mkasm="go run mkasm_darwin.go"
|
||||
;;
|
||||
darwin_amd64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go $(xcrun --show-sdk-path --sdk macosx)/usr/include/sys/syscall.h"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
mkasm="go run mkasm_darwin.go"
|
||||
;;
|
||||
darwin_arm)
|
||||
mkerrors="$mkerrors"
|
||||
mksyscall="go run mksyscall.go -l32"
|
||||
mksysnum="go run mksysnum.go $(xcrun --show-sdk-path --sdk iphoneos)/usr/include/sys/syscall.h"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
mkasm="go run mkasm_darwin.go"
|
||||
;;
|
||||
darwin_arm64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksysnum="go run mksysnum.go $(xcrun --show-sdk-path --sdk iphoneos)/usr/include/sys/syscall.h"
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs"
|
||||
mkasm="go run mkasm_darwin.go"
|
||||
;;
|
||||
|
@ -184,6 +180,15 @@ openbsd_arm64)
|
|||
# API consistent across platforms.
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs -- -fsigned-char"
|
||||
;;
|
||||
openbsd_mips64)
|
||||
mkerrors="$mkerrors -m64"
|
||||
mksyscall="go run mksyscall.go -openbsd"
|
||||
mksysctl="go run mksysctl_openbsd.go"
|
||||
mksysnum="go run mksysnum.go 'https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/src/sys/kern/syscalls.master'"
|
||||
# Let the type of C char be signed for making the bare syscall
|
||||
# API consistent across platforms.
|
||||
mktypes="GOARCH=$GOARCH go tool cgo -godefs -- -fsigned-char"
|
||||
;;
|
||||
solaris_amd64)
|
||||
mksyscall="go run mksyscall_solaris.go"
|
||||
mkerrors="$mkerrors -m64"
|
||||
|
@ -217,8 +222,6 @@ esac
|
|||
# aix/ppc64 script generates files instead of writing to stdin.
|
||||
echo "$mksyscall -tags $GOOS,$GOARCH $syscall_goos $GOOSARCH_in && gofmt -w zsyscall_$GOOSARCH.go && gofmt -w zsyscall_"$GOOSARCH"_gccgo.go && gofmt -w zsyscall_"$GOOSARCH"_gc.go " ;
|
||||
elif [ "$GOOS" == "darwin" ]; then
|
||||
# pre-1.12, direct syscalls
|
||||
echo "$mksyscall -tags $GOOS,$GOARCH,!go1.12 $syscall_goos syscall_darwin_${GOARCH}.1_11.go $GOOSARCH_in |gofmt >zsyscall_$GOOSARCH.1_11.go";
|
||||
# 1.12 and later, syscalls via libSystem
|
||||
echo "$mksyscall -tags $GOOS,$GOARCH,go1.12 $syscall_goos $GOOSARCH_in |gofmt >zsyscall_$GOOSARCH.go";
|
||||
# 1.13 and later, syscalls via libSystem (including syscallPtr)
|
||||
|
|
|
@ -58,6 +58,7 @@ includes_Darwin='
|
|||
#define _DARWIN_USE_64_BIT_INODE
|
||||
#include <stdint.h>
|
||||
#include <sys/attr.h>
|
||||
#include <sys/clonefile.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/event.h>
|
||||
#include <sys/ptrace.h>
|
||||
|
@ -193,6 +194,8 @@ struct ltchars {
|
|||
#include <sys/xattr.h>
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/can.h>
|
||||
#include <linux/can/error.h>
|
||||
#include <linux/can/raw.h>
|
||||
#include <linux/capability.h>
|
||||
#include <linux/cryptouser.h>
|
||||
#include <linux/devlink.h>
|
||||
|
|
|
@ -10,6 +10,8 @@ import (
|
|||
"unsafe"
|
||||
)
|
||||
|
||||
const _SYS_GETDIRENTRIES64 = 344
|
||||
|
||||
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||
// To implement this using libSystem we'd need syscall_syscallPtr for
|
||||
// fdopendir. However, syscallPtr was only added in Go 1.13, so we fall
|
||||
|
@ -20,7 +22,7 @@ func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
|||
} else {
|
||||
p = unsafe.Pointer(&_zero)
|
||||
}
|
||||
r0, _, e1 := Syscall6(SYS_GETDIRENTRIES64, uintptr(fd), uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||
r0, _, e1 := Syscall6(_SYS_GETDIRENTRIES64, uintptr(fd), uintptr(p), uintptr(len(buf)), uintptr(unsafe.Pointer(basep)), 0, 0)
|
||||
n = int(r0)
|
||||
if e1 != 0 {
|
||||
return n, errnoErr(e1)
|
||||
|
|
|
@ -49,6 +49,11 @@ type SockaddrDatalink struct {
|
|||
raw RawSockaddrDatalink
|
||||
}
|
||||
|
||||
// Some external packages rely on SYS___SYSCTL being defined to implement their
|
||||
// own sysctl wrappers. Provide it here, even though direct syscalls are no
|
||||
// longer supported on darwin.
|
||||
const SYS___SYSCTL = 202
|
||||
|
||||
// Translate "kern.hostname" to []_C_int{0,1,2,3}.
|
||||
func nametomib(name string) (mib []_C_int, err error) {
|
||||
const siz = unsafe.Sizeof(mib[0])
|
||||
|
@ -396,6 +401,8 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
|||
//sys Chroot(path string) (err error)
|
||||
//sys ClockGettime(clockid int32, time *Timespec) (err error)
|
||||
//sys Close(fd int) (err error)
|
||||
//sys Clonefile(src string, dst string, flags int) (err error)
|
||||
//sys Clonefileat(srcDirfd int, src string, dstDirfd int, dst string, flags int) (err error)
|
||||
//sys Dup(fd int) (nfd int, err error)
|
||||
//sys Dup2(from int, to int) (err error)
|
||||
//sys Exchangedata(path1 string, path2 string, options int) (err error)
|
||||
|
@ -407,6 +414,7 @@ func Sendfile(outfd int, infd int, offset *int64, count int) (written int, err e
|
|||
//sys Fchmodat(dirfd int, path string, mode uint32, flags int) (err error)
|
||||
//sys Fchown(fd int, uid int, gid int) (err error)
|
||||
//sys Fchownat(dirfd int, path string, uid int, gid int, flags int) (err error)
|
||||
//sys Fclonefileat(srcDirfd int, dstDirfd int, dst string, flags int) (err error)
|
||||
//sys Flock(fd int, how int) (err error)
|
||||
//sys Fpathconf(fd int, name int) (val int, err error)
|
||||
//sys Fsync(fd int) (err error)
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright 2019 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,386,!go1.12
|
||||
|
||||
package unix
|
||||
|
||||
//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
|
@ -44,10 +44,6 @@ func (cmsg *Cmsghdr) SetLen(length int) {
|
|||
|
||||
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
||||
|
||||
// SYS___SYSCTL is used by syscall_bsd.go for all BSDs, but in modern versions
|
||||
// of darwin/386 the syscall is called sysctl instead of __sysctl.
|
||||
const SYS___SYSCTL = SYS_SYSCTL
|
||||
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
// Copyright 2019 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,amd64,!go1.12
|
||||
|
||||
package unix
|
||||
|
||||
//sys Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) = SYS_GETDIRENTRIES64
|
|
@ -44,10 +44,6 @@ func (cmsg *Cmsghdr) SetLen(length int) {
|
|||
|
||||
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno)
|
||||
|
||||
// SYS___SYSCTL is used by syscall_bsd.go for all BSDs, but in modern versions
|
||||
// of darwin/amd64 the syscall is called sysctl instead of __sysctl.
|
||||
const SYS___SYSCTL = SYS_SYSCTL
|
||||
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error) = SYS_FSTAT64
|
||||
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error) = SYS_FSTATAT64
|
||||
//sys Fstatfs(fd int, stat *Statfs_t) (err error) = SYS_FSTATFS64
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2019 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,arm,!go1.12
|
||||
|
||||
package unix
|
||||
|
||||
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
|
@ -44,10 +44,6 @@ func (cmsg *Cmsghdr) SetLen(length int) {
|
|||
|
||||
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno) // sic
|
||||
|
||||
// SYS___SYSCTL is used by syscall_bsd.go for all BSDs, but in modern versions
|
||||
// of darwin/arm the syscall is called sysctl instead of __sysctl.
|
||||
const SYS___SYSCTL = SYS_SYSCTL
|
||||
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error)
|
||||
//sys Fstatfs(fd int, stat *Statfs_t) (err error)
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright 2019 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,arm64,!go1.12
|
||||
|
||||
package unix
|
||||
|
||||
func Getdirentries(fd int, buf []byte, basep *uintptr) (n int, err error) {
|
||||
return 0, ENOSYS
|
||||
}
|
|
@ -46,10 +46,6 @@ func (cmsg *Cmsghdr) SetLen(length int) {
|
|||
|
||||
func Syscall9(num, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err syscall.Errno) // sic
|
||||
|
||||
// SYS___SYSCTL is used by syscall_bsd.go for all BSDs, but in modern versions
|
||||
// of darwin/arm64 the syscall is called sysctl instead of __sysctl.
|
||||
const SYS___SYSCTL = SYS_SYSCTL
|
||||
|
||||
//sys Fstat(fd int, stat *Stat_t) (err error)
|
||||
//sys Fstatat(fd int, path string, stat *Stat_t, flags int) (err error)
|
||||
//sys Fstatfs(fd int, stat *Statfs_t) (err error)
|
||||
|
|
|
@ -24,7 +24,7 @@ func bytes2iovec(bs [][]byte) []Iovec {
|
|||
return iovecs
|
||||
}
|
||||
|
||||
//sys readv(fd int, iovs []Iovec) (n int, err error)
|
||||
//sys readv(fd int, iovs []Iovec) (n int, err error)
|
||||
|
||||
func Readv(fd int, iovs [][]byte) (n int, err error) {
|
||||
iovecs := bytes2iovec(iovs)
|
||||
|
@ -32,7 +32,7 @@ func Readv(fd int, iovs [][]byte) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
//sys preadv(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||
//sys preadv(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||
|
||||
func Preadv(fd int, iovs [][]byte, off int64) (n int, err error) {
|
||||
iovecs := bytes2iovec(iovs)
|
||||
|
@ -40,7 +40,7 @@ func Preadv(fd int, iovs [][]byte, off int64) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
//sys writev(fd int, iovs []Iovec) (n int, err error)
|
||||
//sys writev(fd int, iovs []Iovec) (n int, err error)
|
||||
|
||||
func Writev(fd int, iovs [][]byte) (n int, err error) {
|
||||
iovecs := bytes2iovec(iovs)
|
||||
|
@ -48,10 +48,43 @@ func Writev(fd int, iovs [][]byte) (n int, err error) {
|
|||
return n, err
|
||||
}
|
||||
|
||||
//sys pwritev(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||
//sys pwritev(fd int, iovs []Iovec, off int64) (n int, err error)
|
||||
|
||||
func Pwritev(fd int, iovs [][]byte, off int64) (n int, err error) {
|
||||
iovecs := bytes2iovec(iovs)
|
||||
n, err = pwritev(fd, iovecs, off)
|
||||
return n, err
|
||||
}
|
||||
|
||||
//sys accept4(s int, rsa *RawSockaddrAny, addrlen *_Socklen, flags int) (fd int, err error)
|
||||
|
||||
func Accept4(fd int, flags int) (nfd int, sa Sockaddr, err error) {
|
||||
var rsa RawSockaddrAny
|
||||
var len _Socklen = SizeofSockaddrAny
|
||||
nfd, err = accept4(fd, &rsa, &len, flags)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if len > SizeofSockaddrAny {
|
||||
panic("RawSockaddrAny too small")
|
||||
}
|
||||
sa, err = anyToSockaddr(fd, &rsa)
|
||||
if err != nil {
|
||||
Close(nfd)
|
||||
nfd = 0
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//sysnb pipe2(p *[2]_C_int, flags int) (err error)
|
||||
|
||||
func Pipe2(p []int, flags int) error {
|
||||
if len(p) != 2 {
|
||||
return EINVAL
|
||||
}
|
||||
var pp [2]_C_int
|
||||
err := pipe2(&pp, flags)
|
||||
p[0] = int(pp[0])
|
||||
p[1] = int(pp[1])
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -82,15 +82,6 @@ func IoctlRetInt(fd int, req uint) (int, error) {
|
|||
return int(ret), nil
|
||||
}
|
||||
|
||||
// IoctlSetPointerInt performs an ioctl operation which sets an
|
||||
// integer value on fd, using the specified request number. The ioctl
|
||||
// argument is called with a pointer to the integer value, rather than
|
||||
// passing the integer value directly.
|
||||
func IoctlSetPointerInt(fd int, req uint, value int) error {
|
||||
v := int32(value)
|
||||
return ioctl(fd, req, uintptr(unsafe.Pointer(&v)))
|
||||
}
|
||||
|
||||
func IoctlSetRTCTime(fd int, value *RTCTime) error {
|
||||
err := ioctl(fd, RTC_SET_TIME, uintptr(unsafe.Pointer(value)))
|
||||
runtime.KeepAlive(value)
|
||||
|
@ -145,6 +136,12 @@ func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
|
|||
return openat(dirfd, path, flags|O_LARGEFILE, mode)
|
||||
}
|
||||
|
||||
//sys openat2(dirfd int, path string, open_how *OpenHow, size int) (fd int, err error)
|
||||
|
||||
func Openat2(dirfd int, path string, how *OpenHow) (fd int, err error) {
|
||||
return openat2(dirfd, path, how, SizeofOpenHow)
|
||||
}
|
||||
|
||||
//sys ppoll(fds *PollFd, nfds int, timeout *Timespec, sigmask *Sigset_t) (n int, err error)
|
||||
|
||||
func Ppoll(fds []PollFd, timeout *Timespec, sigmask *Sigset_t) (n int, err error) {
|
||||
|
@ -1111,6 +1108,21 @@ func anyToSockaddr(fd int, rsa *RawSockaddrAny) (Sockaddr, error) {
|
|||
}
|
||||
return sa, nil
|
||||
|
||||
case AF_CAN:
|
||||
pp := (*RawSockaddrCAN)(unsafe.Pointer(rsa))
|
||||
sa := &SockaddrCAN{
|
||||
Ifindex: int(pp.Ifindex),
|
||||
}
|
||||
rx := (*[4]byte)(unsafe.Pointer(&sa.RxID))
|
||||
for i := 0; i < 4; i++ {
|
||||
rx[i] = pp.Addr[i]
|
||||
}
|
||||
tx := (*[4]byte)(unsafe.Pointer(&sa.TxID))
|
||||
for i := 0; i < 4; i++ {
|
||||
tx[i] = pp.Addr[i+4]
|
||||
}
|
||||
return sa, nil
|
||||
|
||||
}
|
||||
return nil, EAFNOSUPPORT
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
package unix
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
|
@ -49,10 +48,6 @@ func Pipe2(p []int, flags int) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
// Underlying system call writes to newoffset via pointer.
|
||||
// Implemented in assembly to avoid allocation.
|
||||
func seek(fd int, offset int64, whence int) (newoffset int64, err syscall.Errno)
|
||||
|
||||
func Seek(fd int, offset int64, whence int) (newoffset int64, err error) {
|
||||
newoffset, errno := seek(fd, offset, whence)
|
||||
if errno != 0 {
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// Copyright 2009 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 arm,!gccgo,linux
|
||||
|
||||
package unix
|
||||
|
||||
import "syscall"
|
||||
|
||||
// Underlying system call writes to newoffset via pointer.
|
||||
// Implemented in assembly to avoid allocation.
|
||||
func seek(fd int, offset int64, whence int) (newoffset int64, err syscall.Errno)
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2019 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.
|
||||
|
||||
package unix
|
||||
|
||||
func setTimespec(sec, nsec int64) Timespec {
|
||||
return Timespec{Sec: sec, Nsec: nsec}
|
||||
}
|
||||
|
||||
func setTimeval(sec, usec int64) Timeval {
|
||||
return Timeval{Sec: sec, Usec: usec}
|
||||
}
|
||||
|
||||
func SetKevent(k *Kevent_t, fd, mode, flags int) {
|
||||
k.Ident = uint64(fd)
|
||||
k.Filter = int16(mode)
|
||||
k.Flags = uint16(flags)
|
||||
}
|
||||
|
||||
func (iov *Iovec) SetLen(length int) {
|
||||
iov.Len = uint64(length)
|
||||
}
|
||||
|
||||
func (msghdr *Msghdr) SetControllen(length int) {
|
||||
msghdr.Controllen = uint32(length)
|
||||
}
|
||||
|
||||
func (cmsg *Cmsghdr) SetLen(length int) {
|
||||
cmsg.Len = uint32(length)
|
||||
}
|
||||
|
||||
// SYS___SYSCTL is used by syscall_bsd.go for all BSDs, but in modern versions
|
||||
// of OpenBSD the syscall is called sysctl instead of __sysctl.
|
||||
const SYS___SYSCTL = SYS_SYSCTL
|
|
@ -232,6 +232,8 @@ const (
|
|||
CLOCK_THREAD_CPUTIME_ID = 0x10
|
||||
CLOCK_UPTIME_RAW = 0x8
|
||||
CLOCK_UPTIME_RAW_APPROX = 0x9
|
||||
CLONE_NOFOLLOW = 0x1
|
||||
CLONE_NOOWNERCOPY = 0x2
|
||||
CR0 = 0x0
|
||||
CR1 = 0x1000
|
||||
CR2 = 0x2000
|
||||
|
|
|
@ -232,6 +232,8 @@ const (
|
|||
CLOCK_THREAD_CPUTIME_ID = 0x10
|
||||
CLOCK_UPTIME_RAW = 0x8
|
||||
CLOCK_UPTIME_RAW_APPROX = 0x9
|
||||
CLONE_NOFOLLOW = 0x1
|
||||
CLONE_NOOWNERCOPY = 0x2
|
||||
CR0 = 0x0
|
||||
CR1 = 0x1000
|
||||
CR2 = 0x2000
|
||||
|
|
|
@ -232,6 +232,8 @@ const (
|
|||
CLOCK_THREAD_CPUTIME_ID = 0x10
|
||||
CLOCK_UPTIME_RAW = 0x8
|
||||
CLOCK_UPTIME_RAW_APPROX = 0x9
|
||||
CLONE_NOFOLLOW = 0x1
|
||||
CLONE_NOOWNERCOPY = 0x2
|
||||
CR0 = 0x0
|
||||
CR1 = 0x1000
|
||||
CR2 = 0x2000
|
||||
|
|
|
@ -232,6 +232,8 @@ const (
|
|||
CLOCK_THREAD_CPUTIME_ID = 0x10
|
||||
CLOCK_UPTIME_RAW = 0x8
|
||||
CLOCK_UPTIME_RAW_APPROX = 0x9
|
||||
CLONE_NOFOLLOW = 0x1
|
||||
CLONE_NOOWNERCOPY = 0x2
|
||||
CR0 = 0x0
|
||||
CR1 = 0x1000
|
||||
CR2 = 0x2000
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue