CV_Sobel
This commit is contained in:
@@ -35,6 +35,7 @@ var procCmd = &cobra.Command{
|
|||||||
config.GCONFIG.Log.LogLevel)
|
config.GCONFIG.Log.LogLevel)
|
||||||
|
|
||||||
logrus.SetLevel(config.GCONFIG.Log.LogLevel)
|
logrus.SetLevel(config.GCONFIG.Log.LogLevel)
|
||||||
|
logrus.Info("image-proc version:", Version)
|
||||||
|
|
||||||
godal.RegisterAll()
|
godal.RegisterAll()
|
||||||
|
|
||||||
@@ -61,6 +62,12 @@ var procCmd = &cobra.Command{
|
|||||||
logrus.Fatal(err)
|
logrus.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for i, img := range reg.MssImages {
|
||||||
|
// edge := producer.CV_Sobel(img)
|
||||||
|
// utils.SavePanToGDALGTiff(edge, 0, 0, "data/temp/out_"+strconv.Itoa(i)+".tif", 1.3)
|
||||||
|
// }
|
||||||
|
// return
|
||||||
|
|
||||||
os.MkdirAll(params.OutputDir, 0755)
|
os.MkdirAll(params.OutputDir, 0755)
|
||||||
|
|
||||||
if doLUTRRC {
|
if doLUTRRC {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ var testCmd = &cobra.Command{
|
|||||||
|
|
||||||
mv := gocv.Split(*input0_mat)
|
mv := gocv.Split(*input0_mat)
|
||||||
for i := 0; i < len(mv); i++ {
|
for i := 0; i < len(mv); i++ {
|
||||||
out := producer.FindEdges(mv[i])
|
out := producer.CV_Sobel(mv[i])
|
||||||
utils.SavePanToGDALGTiff(out, 0, 0, "data/temp/out_"+strconv.Itoa(i)+".tif", 1.3)
|
utils.SavePanToGDALGTiff(out, 0, 0, "data/temp/out_"+strconv.Itoa(i)+".tif", 1.3)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -25,9 +25,13 @@ var (
|
|||||||
ShowVersion bool
|
ShowVersion bool
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Version = "v1.2.0-beta"
|
||||||
|
)
|
||||||
|
|
||||||
func version() string {
|
func version() string {
|
||||||
v := fmt.Sprintf(`
|
v := fmt.Sprintf(`
|
||||||
Main Version: v1.2.0-beta
|
Main Version: %s
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
BUILT_ON_IP %s
|
BUILT_ON_IP %s
|
||||||
BUILT_ON_OS %s
|
BUILT_ON_OS %s
|
||||||
@@ -40,7 +44,7 @@ RUNTIME_VER %s
|
|||||||
OpenCV Version 4.9.0
|
OpenCV Version 4.9.0
|
||||||
GDAL Version 3.8.4
|
GDAL Version 3.8.4
|
||||||
---------------------------------------------------------
|
---------------------------------------------------------
|
||||||
`, BuiltOnIP, BuiltOnOs, BuildDate, LatestCommit, Branch, CommitCnt, BuildNumber, RuntimeVer)
|
`, Version, BuiltOnIP, BuiltOnOs, BuildDate, LatestCommit, Branch, CommitCnt, BuildNumber, RuntimeVer)
|
||||||
|
|
||||||
return v
|
return v
|
||||||
}
|
}
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.20
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/airbusgeo/godal v0.0.11
|
github.com/airbusgeo/godal v0.0.11
|
||||||
github.com/duke-git/lancet/v2 v2.3.1
|
github.com/duke-git/lancet/v2 v2.3.3
|
||||||
github.com/dustin/go-humanize v1.0.1
|
github.com/dustin/go-humanize v1.0.1
|
||||||
github.com/fsnotify/fsnotify v1.7.0
|
github.com/fsnotify/fsnotify v1.7.0
|
||||||
github.com/hebl/gofa v1.19.1
|
github.com/hebl/gofa v1.19.1
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -853,6 +853,8 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
|
|||||||
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
|
||||||
github.com/duke-git/lancet/v2 v2.3.1 h1:cYZHQp57CZKP41EFkV/7TGbUrmhjaPMI5vi3Q+9KJNo=
|
github.com/duke-git/lancet/v2 v2.3.1 h1:cYZHQp57CZKP41EFkV/7TGbUrmhjaPMI5vi3Q+9KJNo=
|
||||||
github.com/duke-git/lancet/v2 v2.3.1/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
github.com/duke-git/lancet/v2 v2.3.1/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||||
|
github.com/duke-git/lancet/v2 v2.3.3 h1:OhqzNzkbJBS9ZlWLo/C7g+WSAOAAyNj7p9CAiEHurUc=
|
||||||
|
github.com/duke-git/lancet/v2 v2.3.3/go.mod h1:zGa2R4xswg6EG9I6WnyubDbFO/+A/RROxIbXcwryTsc=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ import (
|
|||||||
// by performing phase correlation on detected
|
// by performing phase correlation on detected
|
||||||
// edges instead of the raw image
|
// edges instead of the raw image
|
||||||
|
|
||||||
func FindEdges(img0 gocv.Mat) gocv.Mat {
|
// SKIP: 多光谱各个波段的边缘检测结果不佳
|
||||||
|
func CV_Canny(img0 gocv.Mat) gocv.Mat {
|
||||||
fmt.Println(img0.Cols(), img0.Rows(), img0.Type().String())
|
fmt.Println(img0.Cols(), img0.Rows(), img0.Type().String())
|
||||||
dst8 := gocv.NewMatWithSize(img0.Rows(), img0.Cols(), gocv.MatTypeCV8U)
|
dst8 := gocv.NewMatWithSize(img0.Rows(), img0.Cols(), gocv.MatTypeCV8U)
|
||||||
defer dst8.Close()
|
defer dst8.Close()
|
||||||
@@ -23,3 +24,20 @@ func FindEdges(img0 gocv.Mat) gocv.Mat {
|
|||||||
dstEdge.ConvertTo(&dstEdge, gocv.MatTypeCV16U)
|
dstEdge.ConvertTo(&dstEdge, gocv.MatTypeCV16U)
|
||||||
return dstEdge
|
return dstEdge
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CV_Sobel(img0 gocv.Mat) gocv.Mat {
|
||||||
|
// x 方向
|
||||||
|
sobelX := gocv.NewMat()
|
||||||
|
gocv.Sobel(img0, &sobelX, gocv.MatTypeCV32F, 1, 0, 5, 1.5, 0, gocv.BorderDefault)
|
||||||
|
sobelX.ConvertTo(&sobelX, gocv.MatTypeCV16U)
|
||||||
|
// y 方向
|
||||||
|
sobelY := gocv.NewMat()
|
||||||
|
gocv.Sobel(img0, &sobelY, gocv.MatTypeCV32F, 0, 1, 5, 1.5, 0, gocv.BorderIsolated)
|
||||||
|
sobelY.ConvertTo(&sobelY, gocv.MatTypeCV16U)
|
||||||
|
// 合并
|
||||||
|
sobelXY := gocv.NewMat()
|
||||||
|
gocv.Sobel(img0, &sobelXY, gocv.MatTypeCV32F, 1, 1, 5, 1.5, 0, gocv.BorderDefault)
|
||||||
|
sobelXY.ConvertTo(&sobelXY, gocv.MatTypeCV16U)
|
||||||
|
|
||||||
|
return sobelY
|
||||||
|
}
|
||||||
|
|||||||
@@ -171,25 +171,41 @@ func (r *Registrator) CalcDownPhaseCorrelation() error {
|
|||||||
blockWidth := r.MssWidth / BlockNW
|
blockWidth := r.MssWidth / BlockNW
|
||||||
|
|
||||||
// 在 MSS 4 个波段上进行配准
|
// 在 MSS 4 个波段上进行配准
|
||||||
err := r.doMSSPhaseCorrelation(r.MssImages[0],
|
err := r.doPhaseCorrelation(r.MssImages[0],
|
||||||
[]gocv.Mat{r.MssImages[0], r.MssImages[1], r.MssImages[2], r.MssImages[3]},
|
[]gocv.Mat{r.MssImages[0], r.MssImages[1], r.MssImages[2], r.MssImages[3]},
|
||||||
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.DoMSSCoRegistration()
|
r.fileterPhaseShift([]float64{64, 64, 64, 64}, true)
|
||||||
|
r.calcMSSDeltaCoeffs(4)
|
||||||
|
r.DoMSSCoRegistration(false)
|
||||||
|
|
||||||
|
// 边缘检测后再做一次配准
|
||||||
|
|
||||||
|
// var mssEdges []gocv.Mat
|
||||||
|
// for band := 0; band < len(r.registeredMssImages); band++ {
|
||||||
|
// edge := CV_Sobel(r.registeredMssImages[band])
|
||||||
|
// mssEdges = append(mssEdges, edge)
|
||||||
|
// }
|
||||||
|
// r.doPhaseCorrelation(mssEdges[0], mssEdges,
|
||||||
|
// r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
||||||
|
// r.fileterPhaseShift([]float64{5, 5, 5, 5}, false)
|
||||||
|
// r.calcMSSDeltaCoeffs(4)
|
||||||
|
// r.DoMSSCoRegistration(true)
|
||||||
|
|
||||||
// 基于 PAN 图像进行配准
|
// 基于 PAN 图像进行配准
|
||||||
r.phaseShifts[0] = make([]PhaseShiftM, 0)
|
err = r.doPhaseCorrelation(downsampledPanImage,
|
||||||
r.deltaXCoeffs[0] = make([]float64, 0)
|
|
||||||
r.deltaYCoeffs[0] = make([]float64, 0)
|
|
||||||
err = r.doMSSPhaseCorrelation(downsampledPanImage,
|
|
||||||
[]gocv.Mat{r.registeredMssImages[0]},
|
[]gocv.Mat{r.registeredMssImages[0]},
|
||||||
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return r.DoPANCoRegistration()
|
r.fileterPhaseShift([]float64{30.0}, true)
|
||||||
|
r.calcMSSDeltaCoeffs(1)
|
||||||
|
r.DoPANCoRegistration()
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将MSS升采样采样后计算相位相关的偏移量
|
// 将MSS升采样采样后计算相位相关的偏移量
|
||||||
@@ -203,13 +219,13 @@ func (r *Registrator) CalcUpPhaseCorrelation() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 在 MSS 4 个波段上进行配准
|
// 在 MSS 4 个波段上进行配准
|
||||||
err := r.doMSSPhaseCorrelation(r.MssImages[0],
|
err := r.doPhaseCorrelation(r.MssImages[0],
|
||||||
[]gocv.Mat{r.MssImages[0], r.MssImages[1], r.MssImages[2], r.MssImages[3]},
|
[]gocv.Mat{r.MssImages[0], r.MssImages[1], r.MssImages[2], r.MssImages[3]},
|
||||||
r.MssHeight, r.MssWidth, r.MssHeight/BlockNH, r.MssWidth/BlockNW)
|
r.MssHeight, r.MssWidth, r.MssHeight/BlockNH, r.MssWidth/BlockNW)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
r.DoMSSCoRegistration()
|
r.DoMSSCoRegistration(false)
|
||||||
|
|
||||||
upsampledMssImages := make([]gocv.Mat, MssBands)
|
upsampledMssImages := make([]gocv.Mat, MssBands)
|
||||||
for i := 0; i < MssBands; i++ {
|
for i := 0; i < MssBands; i++ {
|
||||||
@@ -227,13 +243,13 @@ func (r *Registrator) CalcUpPhaseCorrelation() error {
|
|||||||
log.Infof("blockHeight=%d, blockWidth=%d", blockHeight, blockWidth)
|
log.Infof("blockHeight=%d, blockWidth=%d", blockHeight, blockWidth)
|
||||||
|
|
||||||
// 基于 PAN 图像进行配准
|
// 基于 PAN 图像进行配准
|
||||||
err = r.doMSSPhaseCorrelation(r.PanImage,
|
r.doPhaseCorrelation(r.PanImage,
|
||||||
[]gocv.Mat{upsampledMssImages[0]},
|
[]gocv.Mat{upsampledMssImages[0]},
|
||||||
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
r.MssHeight, r.MssWidth, blockHeight, blockWidth)
|
||||||
return err
|
return r.DoPANCoRegistration()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registrator) doMSSPhaseCorrelation(base gocv.Mat,
|
func (r *Registrator) doPhaseCorrelation(base gocv.Mat,
|
||||||
mssImages []gocv.Mat,
|
mssImages []gocv.Mat,
|
||||||
height, width,
|
height, width,
|
||||||
blockHeight, blockWidth int) error {
|
blockHeight, blockWidth int) error {
|
||||||
@@ -241,6 +257,8 @@ func (r *Registrator) doMSSPhaseCorrelation(base gocv.Mat,
|
|||||||
|
|
||||||
for band := 0; band < len(mssImages); band++ {
|
for band := 0; band < len(mssImages); band++ {
|
||||||
r.phaseShifts[band] = make([]PhaseShiftM, 0)
|
r.phaseShifts[band] = make([]PhaseShiftM, 0)
|
||||||
|
r.deltaXCoeffs[band] = make([]float64, 0)
|
||||||
|
r.deltaYCoeffs[band] = make([]float64, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
for bh := 0; bh < BlockNH; bh++ {
|
for bh := 0; bh < BlockNH; bh++ {
|
||||||
@@ -306,7 +324,7 @@ func (r *Registrator) doMSSPhaseCorrelation(base gocv.Mat,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.calcMSSDeltaCoeffs(len(mssImages))
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registrator) Clean() {
|
func (r *Registrator) Clean() {
|
||||||
@@ -335,9 +353,9 @@ func (r *Registrator) calcMSSDeltaCoeffs(bands int) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 经验值过滤
|
// 经验值过滤
|
||||||
if shift.dy < 64.0 {
|
// if shift.dy < 64.0 {
|
||||||
continue
|
// continue
|
||||||
}
|
// }
|
||||||
|
|
||||||
effectShift++
|
effectShift++
|
||||||
cx = append(cx, float64(shift.Block.coord.X+shift.Block.width/2)) // MSS 块在X方向没有分块
|
cx = append(cx, float64(shift.Block.coord.X+shift.Block.width/2)) // MSS 块在X方向没有分块
|
||||||
@@ -355,12 +373,12 @@ func (r *Registrator) calcMSSDeltaCoeffs(bands int) error {
|
|||||||
var err error
|
var err error
|
||||||
if r.deltaXCoeffs[i], err = PolynomialFit(cx, dx, 1); err != nil {
|
if r.deltaXCoeffs[i], err = PolynomialFit(cx, dx, 1); err != nil {
|
||||||
log.Error("Error fitting deltaX coeffs: ", err)
|
log.Error("Error fitting deltaX coeffs: ", err)
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.deltaYCoeffs[i], err = PolynomialFit(cx, dy, 2); err != nil {
|
if r.deltaYCoeffs[i], err = PolynomialFit(cx, dy, 2); err != nil {
|
||||||
log.Error("Error fitting deltaY coeffs: ", err)
|
log.Error("Error fitting deltaY coeffs: ", err)
|
||||||
return err
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -379,11 +397,13 @@ func (r *Registrator) calcMSSDeltaCoeffs(bands int) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Registrator) DoMSSCoRegistration() error {
|
func (r *Registrator) DoMSSCoRegistration(byEdge bool) error {
|
||||||
for band := 0; band < MssBands; band++ {
|
for band := 0; band < MssBands; band++ {
|
||||||
if len(r.deltaXCoeffs[band]) < 2 || len(r.deltaYCoeffs[band]) < 3 {
|
if len(r.deltaXCoeffs[band]) < 2 || len(r.deltaYCoeffs[band]) < 3 {
|
||||||
log.Errorf("delta coefficients not calculated, skip co-registration %d", band)
|
log.Errorf("delta coefficients not calculated, skip co-registration %d", band+1)
|
||||||
r.registeredMssImages[band] = r.MssImages[band].Clone()
|
if !byEdge {
|
||||||
|
r.registeredMssImages[band] = r.MssImages[band].Clone()
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -408,13 +428,22 @@ func (r *Registrator) DoMSSCoRegistration() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Println("co-registration for band", band+1)
|
log.Println("co-registration for band", band+1)
|
||||||
r.registeredMssImages[band] = gocv.NewMatWithSize(r.MssHeight, r.MssWidth, gocv.MatTypeCV16UC1)
|
if !byEdge {
|
||||||
gocv.Remap(r.MssImages[band],
|
r.registeredMssImages[band] = gocv.NewMatWithSize(r.MssHeight, r.MssWidth, gocv.MatTypeCV16UC1)
|
||||||
&r.registeredMssImages[band],
|
gocv.Remap(r.MssImages[band],
|
||||||
&mapX, &mapY,
|
&r.registeredMssImages[band],
|
||||||
gocv.InterpolationCubic,
|
&mapX, &mapY,
|
||||||
gocv.BorderConstant,
|
gocv.InterpolationCubic,
|
||||||
color.RGBA{0, 0, 0, 0})
|
gocv.BorderConstant,
|
||||||
|
color.RGBA{0, 0, 0, 0})
|
||||||
|
} else {
|
||||||
|
gocv.Remap(r.registeredMssImages[band],
|
||||||
|
&r.registeredMssImages[band],
|
||||||
|
&mapX, &mapY,
|
||||||
|
gocv.InterpolationCubic,
|
||||||
|
gocv.BorderConstant,
|
||||||
|
color.RGBA{0, 0, 0, 0})
|
||||||
|
}
|
||||||
|
|
||||||
mapX.Close()
|
mapX.Close()
|
||||||
mapY.Close()
|
mapY.Close()
|
||||||
@@ -458,7 +487,6 @@ func (r *Registrator) DoPANCoRegistration() error {
|
|||||||
|
|
||||||
log.Println("co-registration for MSS (Align with PAN)")
|
log.Println("co-registration for MSS (Align with PAN)")
|
||||||
for i := 0; i < MssBands; i++ {
|
for i := 0; i < MssBands; i++ {
|
||||||
log.Infof("r.registeredMssImages[%d]: %v", i, r.registeredMssImages[i].Size())
|
|
||||||
registeredMSS := gocv.NewMatWithSize(r.MssHeight, r.MssWidth, gocv.MatTypeCV16UC1)
|
registeredMSS := gocv.NewMatWithSize(r.MssHeight, r.MssWidth, gocv.MatTypeCV16UC1)
|
||||||
gocv.Remap(r.registeredMssImages[i],
|
gocv.Remap(r.registeredMssImages[i],
|
||||||
®isteredMSS,
|
®isteredMSS,
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
package producer
|
package producer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"image"
|
"image"
|
||||||
|
|
||||||
|
"github.com/duke-git/lancet/v2/slice"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gocv.io/x/gocv"
|
"gocv.io/x/gocv"
|
||||||
)
|
)
|
||||||
@@ -40,3 +42,25 @@ func (r *Registrator) calculateBlockPhaseShift(panBlock, mssBlock gocv.Mat) (goc
|
|||||||
|
|
||||||
return shift, response
|
return shift, response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Registrator) fileterPhaseShift(thredholds []float64, greaterThan bool) error {
|
||||||
|
if len(thredholds) > 4 {
|
||||||
|
return errors.New("thredholds length should be less than 4")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < len(thredholds); i++ {
|
||||||
|
th := thredholds[i]
|
||||||
|
r.phaseShifts[i] = slice.Filter(r.phaseShifts[i], func(i int, value PhaseShiftM) bool {
|
||||||
|
if value.response > 0.999999 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if greaterThan {
|
||||||
|
return value.dy > float32(th)
|
||||||
|
}
|
||||||
|
return value.dy < float32(th)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
20
vendor/git.sr.ht/~sbinet/gg/LICENSE.md
vendored
20
vendor/git.sr.ht/~sbinet/gg/LICENSE.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
Copyright (C) 2022 The gg Authors
|
|
||||||
Copyright (C) 2016 Michael Fogleman
|
|
||||||
|
|
||||||
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.
|
|
||||||
239
vendor/git.sr.ht/~sbinet/gg/README.md
vendored
239
vendor/git.sr.ht/~sbinet/gg/README.md
vendored
@@ -1,239 +0,0 @@
|
|||||||
# Go Graphics
|
|
||||||
|
|
||||||
[](https://builds.sr.ht/~sbinet/gg?)
|
|
||||||
[](https://pkg.go.dev/git.sr.ht/~sbinet/gg)
|
|
||||||
[](https://godocs.io/git.sr.ht/~sbinet/gg)
|
|
||||||
|
|
||||||
`gg` is a library for rendering 2D graphics in pure Go.
|
|
||||||
|
|
||||||
`git.sr.ht/~sbinet/gg` is a fork of [fogleman/gg](https://github.com/fogleman/gg) which doesn't seem to be maintained (as of January 2022).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
go get -u git.sr.ht/~sbinet/gg
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
- godoc: https://godoc.org/git.sr.ht/~sbinet/gg
|
|
||||||
- pkg.go.dev: https://pkg.go.dev/git.sr.ht/~sbinet/gg?tab=doc
|
|
||||||
|
|
||||||
## Hello, Circle!
|
|
||||||
|
|
||||||
Look how easy!
|
|
||||||
|
|
||||||
[embedmd]:# (examples/circle_example_test.go go /func ExampleCircle/ /\n}/)
|
|
||||||
```go
|
|
||||||
func ExampleCircle() {
|
|
||||||
dc := gg.NewContext(1000, 1000)
|
|
||||||
dc.DrawCircle(500, 500, 400)
|
|
||||||
dc.SetRGB(0, 0, 0)
|
|
||||||
dc.Fill()
|
|
||||||
|
|
||||||
err := dc.SavePNG("testdata/circle.png")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not save to file: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
There are [lots of examples](https://git.sr.ht/~sbinet/gg/tree/main/examples) included. They're mostly for testing the code, but they're good for learning, too.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Creating Contexts
|
|
||||||
|
|
||||||
There are a few ways of creating a context.
|
|
||||||
|
|
||||||
```go
|
|
||||||
NewContext(width, height int) *Context
|
|
||||||
NewContextForImage(im image.Image) *Context
|
|
||||||
NewContextForRGBA(im *image.RGBA) *Context
|
|
||||||
```
|
|
||||||
|
|
||||||
## Drawing Functions
|
|
||||||
|
|
||||||
Ever used a graphics library that didn't have functions for drawing rectangles
|
|
||||||
or circles? What a pain!
|
|
||||||
|
|
||||||
```go
|
|
||||||
DrawPoint(x, y, r float64)
|
|
||||||
DrawLine(x1, y1, x2, y2 float64)
|
|
||||||
DrawRectangle(x, y, w, h float64)
|
|
||||||
DrawRoundedRectangle(x, y, w, h, r float64)
|
|
||||||
DrawCircle(x, y, r float64)
|
|
||||||
DrawArc(x, y, r, angle1, angle2 float64)
|
|
||||||
DrawEllipse(x, y, rx, ry float64)
|
|
||||||
DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64)
|
|
||||||
DrawRegularPolygon(n int, x, y, r, rotation float64)
|
|
||||||
DrawImage(im image.Image, x, y int)
|
|
||||||
DrawImageAnchored(im image.Image, x, y int, ax, ay float64)
|
|
||||||
SetPixel(x, y int)
|
|
||||||
|
|
||||||
MoveTo(x, y float64)
|
|
||||||
LineTo(x, y float64)
|
|
||||||
QuadraticTo(x1, y1, x2, y2 float64)
|
|
||||||
CubicTo(x1, y1, x2, y2, x3, y3 float64)
|
|
||||||
ClosePath()
|
|
||||||
ClearPath()
|
|
||||||
NewSubPath()
|
|
||||||
|
|
||||||
Clear()
|
|
||||||
Stroke()
|
|
||||||
Fill()
|
|
||||||
StrokePreserve()
|
|
||||||
FillPreserve()
|
|
||||||
```
|
|
||||||
|
|
||||||
It is often desired to center an image at a point. Use `DrawImageAnchored` with `ax` and `ay` set to 0.5 to do this. Use 0 to left or top align. Use 1 to right or bottom align. `DrawStringAnchored` does the same for text, so you don't need to call `MeasureString` yourself.
|
|
||||||
|
|
||||||
## Text Functions
|
|
||||||
|
|
||||||
It will even do word wrap for you!
|
|
||||||
|
|
||||||
```go
|
|
||||||
DrawString(s string, x, y float64)
|
|
||||||
DrawStringAnchored(s string, x, y, ax, ay float64)
|
|
||||||
DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align)
|
|
||||||
MeasureString(s string) (w, h float64)
|
|
||||||
MeasureMultilineString(s string, lineSpacing float64) (w, h float64)
|
|
||||||
WordWrap(s string, w float64) []string
|
|
||||||
SetFontFace(fontFace font.Face)
|
|
||||||
LoadFontFace(path string, points float64) error
|
|
||||||
LoadFontFaceFromBytes(raw []byte, points float64) error
|
|
||||||
LoadFontFaceFromFS(fsys fs.FS, path string, points float64) error
|
|
||||||
```
|
|
||||||
|
|
||||||
## Color Functions
|
|
||||||
|
|
||||||
Colors can be set in several different ways for your convenience.
|
|
||||||
|
|
||||||
```go
|
|
||||||
SetRGB(r, g, b float64)
|
|
||||||
SetRGBA(r, g, b, a float64)
|
|
||||||
SetRGB255(r, g, b int)
|
|
||||||
SetRGBA255(r, g, b, a int)
|
|
||||||
SetColor(c color.Color)
|
|
||||||
SetHexColor(x string)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stroke & Fill Options
|
|
||||||
|
|
||||||
```go
|
|
||||||
SetLineWidth(lineWidth float64)
|
|
||||||
SetLineCap(lineCap LineCap)
|
|
||||||
SetLineJoin(lineJoin LineJoin)
|
|
||||||
SetDash(dashes ...float64)
|
|
||||||
SetDashOffset(offset float64)
|
|
||||||
SetFillRule(fillRule FillRule)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Gradients & Patterns
|
|
||||||
|
|
||||||
`gg` supports linear, radial and conic gradients and surface patterns. You can also implement your own patterns.
|
|
||||||
|
|
||||||
```go
|
|
||||||
SetFillStyle(pattern Pattern)
|
|
||||||
SetStrokeStyle(pattern Pattern)
|
|
||||||
NewSolidPattern(color color.Color)
|
|
||||||
NewLinearGradient(x0, y0, x1, y1 float64)
|
|
||||||
NewRadialGradient(x0, y0, r0, x1, y1, r1 float64)
|
|
||||||
NewConicGradient(cx, cy, deg float64)
|
|
||||||
NewSurfacePattern(im image.Image, op RepeatOp)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Transformation Functions
|
|
||||||
|
|
||||||
```go
|
|
||||||
Identity()
|
|
||||||
Translate(x, y float64)
|
|
||||||
Scale(x, y float64)
|
|
||||||
Rotate(angle float64)
|
|
||||||
Shear(x, y float64)
|
|
||||||
ScaleAbout(sx, sy, x, y float64)
|
|
||||||
RotateAbout(angle, x, y float64)
|
|
||||||
ShearAbout(sx, sy, x, y float64)
|
|
||||||
TransformPoint(x, y float64) (tx, ty float64)
|
|
||||||
InvertY()
|
|
||||||
```
|
|
||||||
|
|
||||||
It is often desired to rotate or scale about a point that is not the origin. The functions `RotateAbout`, `ScaleAbout`, `ShearAbout` are provided as a convenience.
|
|
||||||
|
|
||||||
`InvertY` is provided in case Y should increase from bottom to top vs. the default top to bottom.
|
|
||||||
|
|
||||||
## Stack Functions
|
|
||||||
|
|
||||||
Save and restore the state of the context. These can be nested.
|
|
||||||
|
|
||||||
```go
|
|
||||||
Push()
|
|
||||||
Pop()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Clipping Functions
|
|
||||||
|
|
||||||
Use clipping regions to restrict drawing operations to an area that you
|
|
||||||
defined using paths.
|
|
||||||
|
|
||||||
```go
|
|
||||||
Clip()
|
|
||||||
ClipPreserve()
|
|
||||||
ResetClip()
|
|
||||||
AsMask() *image.Alpha
|
|
||||||
SetMask(mask *image.Alpha)
|
|
||||||
InvertMask()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
Sometimes you just don't want to write these yourself.
|
|
||||||
|
|
||||||
```go
|
|
||||||
Radians(degrees float64) float64
|
|
||||||
Degrees(radians float64) float64
|
|
||||||
LoadImage(path string) (image.Image, error)
|
|
||||||
LoadPNG(path string) (image.Image, error)
|
|
||||||
SavePNG(path string, im image.Image) error
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Another Example
|
|
||||||
|
|
||||||
See the output of this example below.
|
|
||||||
|
|
||||||
[embedmd]:# (examples/ellipse_example_test.go go /func ExampleEllipse/ /\n}/)
|
|
||||||
```go
|
|
||||||
func ExampleEllipse() {
|
|
||||||
const S = 1024
|
|
||||||
dc := gg.NewContext(S, S)
|
|
||||||
dc.SetRGBA(0, 0, 0, 0.1)
|
|
||||||
for i := 0; i < 360; i += 15 {
|
|
||||||
dc.Push()
|
|
||||||
dc.RotateAbout(gg.Radians(float64(i)), S/2, S/2)
|
|
||||||
dc.DrawEllipse(S/2, S/2, S*7/16, S/8)
|
|
||||||
dc.Fill()
|
|
||||||
dc.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
im, err := gg.LoadImage("testdata/gopher.png")
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
dc.DrawImageAnchored(im, S/2, S/2, 0.5, 0.5)
|
|
||||||
|
|
||||||
err = dc.SavePNG("testdata/ellipse.png")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("could not save to file: %+v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
63
vendor/git.sr.ht/~sbinet/gg/bezier.go
vendored
63
vendor/git.sr.ht/~sbinet/gg/bezier.go
vendored
@@ -1,63 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
func quadratic(x0, y0, x1, y1, x2, y2, t float64) (x, y float64) {
|
|
||||||
u := 1 - t
|
|
||||||
a := u * u
|
|
||||||
b := 2 * u * t
|
|
||||||
c := t * t
|
|
||||||
x = a*x0 + b*x1 + c*x2
|
|
||||||
y = a*y0 + b*y1 + c*y2
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func QuadraticBezier(x0, y0, x1, y1, x2, y2 float64) []Point {
|
|
||||||
l := (math.Hypot(x1-x0, y1-y0) +
|
|
||||||
math.Hypot(x2-x1, y2-y1))
|
|
||||||
n := int(l + 0.5)
|
|
||||||
if n < 4 {
|
|
||||||
n = 4
|
|
||||||
}
|
|
||||||
d := float64(n) - 1
|
|
||||||
result := make([]Point, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t := float64(i) / d
|
|
||||||
x, y := quadratic(x0, y0, x1, y1, x2, y2, t)
|
|
||||||
result[i] = Point{x, y}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func cubic(x0, y0, x1, y1, x2, y2, x3, y3, t float64) (x, y float64) {
|
|
||||||
u := 1 - t
|
|
||||||
a := u * u * u
|
|
||||||
b := 3 * u * u * t
|
|
||||||
c := 3 * u * t * t
|
|
||||||
d := t * t * t
|
|
||||||
x = a*x0 + b*x1 + c*x2 + d*x3
|
|
||||||
y = a*y0 + b*y1 + c*y2 + d*y3
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3 float64) []Point {
|
|
||||||
l := (math.Hypot(x1-x0, y1-y0) +
|
|
||||||
math.Hypot(x2-x1, y2-y1) +
|
|
||||||
math.Hypot(x3-x2, y3-y2))
|
|
||||||
n := int(l + 0.5)
|
|
||||||
if n < 4 {
|
|
||||||
n = 4
|
|
||||||
}
|
|
||||||
d := float64(n) - 1
|
|
||||||
result := make([]Point, n)
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
t := float64(i) / d
|
|
||||||
x, y := cubic(x0, y0, x1, y1, x2, y2, x3, y3, t)
|
|
||||||
result[i] = Point{x, y}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
964
vendor/git.sr.ht/~sbinet/gg/context.go
vendored
964
vendor/git.sr.ht/~sbinet/gg/context.go
vendored
@@ -1,964 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
"image/jpeg"
|
|
||||||
"image/png"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"math"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/raster"
|
|
||||||
"golang.org/x/image/draw"
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/basicfont"
|
|
||||||
"golang.org/x/image/math/f64"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LineCap int
|
|
||||||
|
|
||||||
const (
|
|
||||||
LineCapRound LineCap = iota
|
|
||||||
LineCapButt
|
|
||||||
LineCapSquare
|
|
||||||
)
|
|
||||||
|
|
||||||
type LineJoin int
|
|
||||||
|
|
||||||
const (
|
|
||||||
LineJoinRound LineJoin = iota
|
|
||||||
LineJoinBevel
|
|
||||||
)
|
|
||||||
|
|
||||||
type FillRule int
|
|
||||||
|
|
||||||
const (
|
|
||||||
FillRuleWinding FillRule = iota
|
|
||||||
FillRuleEvenOdd
|
|
||||||
)
|
|
||||||
|
|
||||||
type Align int
|
|
||||||
|
|
||||||
const (
|
|
||||||
AlignLeft Align = iota
|
|
||||||
AlignCenter
|
|
||||||
AlignRight
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
defaultFillStyle = NewSolidPattern(color.White)
|
|
||||||
defaultStrokeStyle = NewSolidPattern(color.Black)
|
|
||||||
)
|
|
||||||
|
|
||||||
type Context struct {
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
rasterizer *raster.Rasterizer
|
|
||||||
im *image.RGBA
|
|
||||||
mask *image.Alpha
|
|
||||||
color color.Color
|
|
||||||
fillPattern Pattern
|
|
||||||
strokePattern Pattern
|
|
||||||
strokePath raster.Path
|
|
||||||
fillPath raster.Path
|
|
||||||
start Point
|
|
||||||
current Point
|
|
||||||
hasCurrent bool
|
|
||||||
dashes []float64
|
|
||||||
dashOffset float64
|
|
||||||
lineWidth float64
|
|
||||||
lineCap LineCap
|
|
||||||
lineJoin LineJoin
|
|
||||||
fillRule FillRule
|
|
||||||
fontFace font.Face
|
|
||||||
fontHeight float64
|
|
||||||
matrix Matrix
|
|
||||||
stack []*Context
|
|
||||||
interp draw.Interpolator
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContext creates a new image.RGBA with the specified width and height
|
|
||||||
// and prepares a context for rendering onto that image.
|
|
||||||
func NewContext(width, height int) *Context {
|
|
||||||
return NewContextForRGBA(image.NewRGBA(image.Rect(0, 0, width, height)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContextForImage copies the specified image into a new image.RGBA
|
|
||||||
// and prepares a context for rendering onto that image.
|
|
||||||
func NewContextForImage(im image.Image) *Context {
|
|
||||||
return NewContextForRGBA(imageToRGBA(im))
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewContextForRGBA prepares a context for rendering onto the specified image.
|
|
||||||
// No copy is made.
|
|
||||||
func NewContextForRGBA(im *image.RGBA) *Context {
|
|
||||||
w := im.Bounds().Size().X
|
|
||||||
h := im.Bounds().Size().Y
|
|
||||||
return &Context{
|
|
||||||
width: w,
|
|
||||||
height: h,
|
|
||||||
rasterizer: raster.NewRasterizer(w, h),
|
|
||||||
im: im,
|
|
||||||
color: color.Transparent,
|
|
||||||
fillPattern: defaultFillStyle,
|
|
||||||
strokePattern: defaultStrokeStyle,
|
|
||||||
lineWidth: 1,
|
|
||||||
fillRule: FillRuleWinding,
|
|
||||||
fontFace: basicfont.Face7x13,
|
|
||||||
fontHeight: 13,
|
|
||||||
matrix: Identity(),
|
|
||||||
interp: draw.BiLinear,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCurrentPoint will return the current point and if there is a current point.
|
|
||||||
// The point will have been transformed by the context's transformation matrix.
|
|
||||||
func (dc *Context) GetCurrentPoint() (Point, bool) {
|
|
||||||
if dc.hasCurrent {
|
|
||||||
return dc.current, true
|
|
||||||
}
|
|
||||||
return Point{}, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Image returns the image that has been drawn by this context.
|
|
||||||
func (dc *Context) Image() image.Image {
|
|
||||||
return dc.im
|
|
||||||
}
|
|
||||||
|
|
||||||
// Width returns the width of the image in pixels.
|
|
||||||
func (dc *Context) Width() int {
|
|
||||||
return dc.width
|
|
||||||
}
|
|
||||||
|
|
||||||
// Height returns the height of the image in pixels.
|
|
||||||
func (dc *Context) Height() int {
|
|
||||||
return dc.height
|
|
||||||
}
|
|
||||||
|
|
||||||
// SavePNG encodes the image as a PNG and writes it to disk.
|
|
||||||
func (dc *Context) SavePNG(path string) error {
|
|
||||||
return SavePNG(path, dc.im)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveJPG encodes the image as a JPG and writes it to disk.
|
|
||||||
func (dc *Context) SaveJPG(path string, quality int) error {
|
|
||||||
return SaveJPG(path, dc.im, quality)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodePNG encodes the image as a PNG and writes it to the provided io.Writer.
|
|
||||||
func (dc *Context) EncodePNG(w io.Writer) error {
|
|
||||||
return png.Encode(w, dc.im)
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeJPG encodes the image as a JPG and writes it to the provided io.Writer
|
|
||||||
// in JPEG 4:2:0 baseline format with the given options.
|
|
||||||
// Default parameters are used if a nil *jpeg.Options is passed.
|
|
||||||
func (dc *Context) EncodeJPG(w io.Writer, o *jpeg.Options) error {
|
|
||||||
return jpeg.Encode(w, dc.im, o)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDash sets the current dash pattern to use. Call with zero arguments to
|
|
||||||
// disable dashes. The values specify the lengths of each dash, with
|
|
||||||
// alternating on and off lengths.
|
|
||||||
func (dc *Context) SetDash(dashes ...float64) {
|
|
||||||
dc.dashes = dashes
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDashOffset sets the initial offset into the dash pattern to use when
|
|
||||||
// stroking dashed paths.
|
|
||||||
func (dc *Context) SetDashOffset(offset float64) {
|
|
||||||
dc.dashOffset = offset
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineWidth(lineWidth float64) {
|
|
||||||
dc.lineWidth = lineWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineCap(lineCap LineCap) {
|
|
||||||
dc.lineCap = lineCap
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineCapRound() {
|
|
||||||
dc.lineCap = LineCapRound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineCapButt() {
|
|
||||||
dc.lineCap = LineCapButt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineCapSquare() {
|
|
||||||
dc.lineCap = LineCapSquare
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineJoin(lineJoin LineJoin) {
|
|
||||||
dc.lineJoin = lineJoin
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineJoinRound() {
|
|
||||||
dc.lineJoin = LineJoinRound
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetLineJoinBevel() {
|
|
||||||
dc.lineJoin = LineJoinBevel
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetFillRule(fillRule FillRule) {
|
|
||||||
dc.fillRule = fillRule
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetFillRuleWinding() {
|
|
||||||
dc.fillRule = FillRuleWinding
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) SetFillRuleEvenOdd() {
|
|
||||||
dc.fillRule = FillRuleEvenOdd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Color Setters
|
|
||||||
|
|
||||||
func (dc *Context) setFillAndStrokeColor(c color.Color) {
|
|
||||||
dc.color = c
|
|
||||||
dc.fillPattern = NewSolidPattern(c)
|
|
||||||
dc.strokePattern = NewSolidPattern(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetFillStyle sets current fill style
|
|
||||||
func (dc *Context) SetFillStyle(pattern Pattern) {
|
|
||||||
// if pattern is SolidPattern, also change dc.color(for dc.Clear, dc.drawString)
|
|
||||||
if fillStyle, ok := pattern.(*solidPattern); ok {
|
|
||||||
dc.color = fillStyle.color
|
|
||||||
}
|
|
||||||
dc.fillPattern = pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetStrokeStyle sets current stroke style
|
|
||||||
func (dc *Context) SetStrokeStyle(pattern Pattern) {
|
|
||||||
dc.strokePattern = pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetColor sets the current color(for both fill and stroke).
|
|
||||||
func (dc *Context) SetColor(c color.Color) {
|
|
||||||
dc.setFillAndStrokeColor(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHexColor sets the current color using a hex string. The leading pound
|
|
||||||
// sign (#) is optional. Both 3- and 6-digit variations are supported. 8 digits
|
|
||||||
// may be provided to set the alpha value as well.
|
|
||||||
func (dc *Context) SetHexColor(x string) {
|
|
||||||
r, g, b, a := parseHexColor(x)
|
|
||||||
dc.SetRGBA255(r, g, b, a)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRGBA255 sets the current color. r, g, b, a values should be between 0 and
|
|
||||||
// 255, inclusive.
|
|
||||||
func (dc *Context) SetRGBA255(r, g, b, a int) {
|
|
||||||
dc.color = color.NRGBA{uint8(r), uint8(g), uint8(b), uint8(a)}
|
|
||||||
dc.setFillAndStrokeColor(dc.color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRGB255 sets the current color. r, g, b values should be between 0 and 255,
|
|
||||||
// inclusive. Alpha will be set to 255 (fully opaque).
|
|
||||||
func (dc *Context) SetRGB255(r, g, b int) {
|
|
||||||
dc.SetRGBA255(r, g, b, 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRGBA sets the current color. r, g, b, a values should be between 0 and 1,
|
|
||||||
// inclusive.
|
|
||||||
func (dc *Context) SetRGBA(r, g, b, a float64) {
|
|
||||||
dc.color = color.NRGBA{
|
|
||||||
uint8(r * 255),
|
|
||||||
uint8(g * 255),
|
|
||||||
uint8(b * 255),
|
|
||||||
uint8(a * 255),
|
|
||||||
}
|
|
||||||
dc.setFillAndStrokeColor(dc.color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRGB sets the current color. r, g, b values should be between 0 and 1,
|
|
||||||
// inclusive. Alpha will be set to 1 (fully opaque).
|
|
||||||
func (dc *Context) SetRGB(r, g, b float64) {
|
|
||||||
dc.SetRGBA(r, g, b, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path Manipulation
|
|
||||||
|
|
||||||
// MoveTo starts a new subpath within the current path starting at the
|
|
||||||
// specified point.
|
|
||||||
func (dc *Context) MoveTo(x, y float64) {
|
|
||||||
if dc.hasCurrent {
|
|
||||||
dc.fillPath.Add1(dc.start.Fixed())
|
|
||||||
}
|
|
||||||
x, y = dc.TransformPoint(x, y)
|
|
||||||
p := Point{x, y}
|
|
||||||
dc.strokePath.Start(p.Fixed())
|
|
||||||
dc.fillPath.Start(p.Fixed())
|
|
||||||
dc.start = p
|
|
||||||
dc.current = p
|
|
||||||
dc.hasCurrent = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// LineTo adds a line segment to the current path starting at the current
|
|
||||||
// point. If there is no current point, it is equivalent to MoveTo(x, y)
|
|
||||||
func (dc *Context) LineTo(x, y float64) {
|
|
||||||
if !dc.hasCurrent {
|
|
||||||
dc.MoveTo(x, y)
|
|
||||||
} else {
|
|
||||||
x, y = dc.TransformPoint(x, y)
|
|
||||||
p := Point{x, y}
|
|
||||||
dc.strokePath.Add1(p.Fixed())
|
|
||||||
dc.fillPath.Add1(p.Fixed())
|
|
||||||
dc.current = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// QuadraticTo adds a quadratic bezier curve to the current path starting at
|
|
||||||
// the current point. If there is no current point, it first performs
|
|
||||||
// MoveTo(x1, y1)
|
|
||||||
func (dc *Context) QuadraticTo(x1, y1, x2, y2 float64) {
|
|
||||||
if !dc.hasCurrent {
|
|
||||||
dc.MoveTo(x1, y1)
|
|
||||||
}
|
|
||||||
x1, y1 = dc.TransformPoint(x1, y1)
|
|
||||||
x2, y2 = dc.TransformPoint(x2, y2)
|
|
||||||
p1 := Point{x1, y1}
|
|
||||||
p2 := Point{x2, y2}
|
|
||||||
dc.strokePath.Add2(p1.Fixed(), p2.Fixed())
|
|
||||||
dc.fillPath.Add2(p1.Fixed(), p2.Fixed())
|
|
||||||
dc.current = p2
|
|
||||||
}
|
|
||||||
|
|
||||||
// CubicTo adds a cubic bezier curve to the current path starting at the
|
|
||||||
// current point. If there is no current point, it first performs
|
|
||||||
// MoveTo(x1, y1). Because freetype/raster does not support cubic beziers,
|
|
||||||
// this is emulated with many small line segments.
|
|
||||||
func (dc *Context) CubicTo(x1, y1, x2, y2, x3, y3 float64) {
|
|
||||||
if !dc.hasCurrent {
|
|
||||||
dc.MoveTo(x1, y1)
|
|
||||||
}
|
|
||||||
x0, y0 := dc.current.X, dc.current.Y
|
|
||||||
x1, y1 = dc.TransformPoint(x1, y1)
|
|
||||||
x2, y2 = dc.TransformPoint(x2, y2)
|
|
||||||
x3, y3 = dc.TransformPoint(x3, y3)
|
|
||||||
points := CubicBezier(x0, y0, x1, y1, x2, y2, x3, y3)
|
|
||||||
previous := dc.current.Fixed()
|
|
||||||
for _, p := range points[1:] {
|
|
||||||
f := p.Fixed()
|
|
||||||
if f == previous {
|
|
||||||
// TODO: this fixes some rendering issues but not all
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
previous = f
|
|
||||||
dc.strokePath.Add1(f)
|
|
||||||
dc.fillPath.Add1(f)
|
|
||||||
dc.current = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClosePath adds a line segment from the current point to the beginning
|
|
||||||
// of the current subpath. If there is no current point, this is a no-op.
|
|
||||||
func (dc *Context) ClosePath() {
|
|
||||||
if dc.hasCurrent {
|
|
||||||
dc.strokePath.Add1(dc.start.Fixed())
|
|
||||||
dc.fillPath.Add1(dc.start.Fixed())
|
|
||||||
dc.current = dc.start
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearPath clears the current path. There is no current point after this
|
|
||||||
// operation.
|
|
||||||
func (dc *Context) ClearPath() {
|
|
||||||
dc.strokePath.Clear()
|
|
||||||
dc.fillPath.Clear()
|
|
||||||
dc.hasCurrent = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSubPath starts a new subpath within the current path. There is no current
|
|
||||||
// point after this operation.
|
|
||||||
func (dc *Context) NewSubPath() {
|
|
||||||
if dc.hasCurrent {
|
|
||||||
dc.fillPath.Add1(dc.start.Fixed())
|
|
||||||
}
|
|
||||||
dc.hasCurrent = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path Drawing
|
|
||||||
|
|
||||||
func (dc *Context) capper() raster.Capper {
|
|
||||||
switch dc.lineCap {
|
|
||||||
case LineCapButt:
|
|
||||||
return raster.ButtCapper
|
|
||||||
case LineCapRound:
|
|
||||||
return raster.RoundCapper
|
|
||||||
case LineCapSquare:
|
|
||||||
return raster.SquareCapper
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) joiner() raster.Joiner {
|
|
||||||
switch dc.lineJoin {
|
|
||||||
case LineJoinBevel:
|
|
||||||
return raster.BevelJoiner
|
|
||||||
case LineJoinRound:
|
|
||||||
return raster.RoundJoiner
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) stroke(painter raster.Painter) {
|
|
||||||
path := dc.strokePath
|
|
||||||
if len(dc.dashes) > 0 {
|
|
||||||
path = dashed(path, dc.dashes, dc.dashOffset)
|
|
||||||
} else {
|
|
||||||
// TODO: this is a temporary workaround to remove tiny segments
|
|
||||||
// that result in rendering issues
|
|
||||||
path = rasterPath(flattenPath(path))
|
|
||||||
}
|
|
||||||
r := dc.rasterizer
|
|
||||||
r.UseNonZeroWinding = true
|
|
||||||
r.Clear()
|
|
||||||
r.AddStroke(path, fix(dc.lineWidth), dc.capper(), dc.joiner())
|
|
||||||
r.Rasterize(painter)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) fill(painter raster.Painter) {
|
|
||||||
path := dc.fillPath
|
|
||||||
if dc.hasCurrent {
|
|
||||||
path = make(raster.Path, len(dc.fillPath))
|
|
||||||
copy(path, dc.fillPath)
|
|
||||||
path.Add1(dc.start.Fixed())
|
|
||||||
}
|
|
||||||
r := dc.rasterizer
|
|
||||||
r.UseNonZeroWinding = dc.fillRule == FillRuleWinding
|
|
||||||
r.Clear()
|
|
||||||
r.AddPath(path)
|
|
||||||
r.Rasterize(painter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrokePreserve strokes the current path with the current color, line width,
|
|
||||||
// line cap, line join and dash settings. The path is preserved after this
|
|
||||||
// operation.
|
|
||||||
func (dc *Context) StrokePreserve() {
|
|
||||||
var painter raster.Painter
|
|
||||||
if dc.mask == nil {
|
|
||||||
if pattern, ok := dc.strokePattern.(*solidPattern); ok {
|
|
||||||
// with a nil mask and a solid color pattern, we can be more efficient
|
|
||||||
// TODO: refactor so we don't have to do this type assertion stuff?
|
|
||||||
p := raster.NewRGBAPainter(dc.im)
|
|
||||||
p.SetColor(pattern.color)
|
|
||||||
painter = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if painter == nil {
|
|
||||||
painter = newPatternPainter(dc.im, dc.mask, dc.strokePattern)
|
|
||||||
}
|
|
||||||
dc.stroke(painter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stroke strokes the current path with the current color, line width,
|
|
||||||
// line cap, line join and dash settings. The path is cleared after this
|
|
||||||
// operation.
|
|
||||||
func (dc *Context) Stroke() {
|
|
||||||
dc.StrokePreserve()
|
|
||||||
dc.ClearPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FillPreserve fills the current path with the current color. Open subpaths
|
|
||||||
// are implicitly closed. The path is preserved after this operation.
|
|
||||||
func (dc *Context) FillPreserve() {
|
|
||||||
var painter raster.Painter
|
|
||||||
if dc.mask == nil {
|
|
||||||
if pattern, ok := dc.fillPattern.(*solidPattern); ok {
|
|
||||||
// with a nil mask and a solid color pattern, we can be more efficient
|
|
||||||
// TODO: refactor so we don't have to do this type assertion stuff?
|
|
||||||
p := raster.NewRGBAPainter(dc.im)
|
|
||||||
p.SetColor(pattern.color)
|
|
||||||
painter = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if painter == nil {
|
|
||||||
painter = newPatternPainter(dc.im, dc.mask, dc.fillPattern)
|
|
||||||
}
|
|
||||||
dc.fill(painter)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fill fills the current path with the current color. Open subpaths
|
|
||||||
// are implicitly closed. The path is cleared after this operation.
|
|
||||||
func (dc *Context) Fill() {
|
|
||||||
dc.FillPreserve()
|
|
||||||
dc.ClearPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClipPreserve updates the clipping region by intersecting the current
|
|
||||||
// clipping region with the current path as it would be filled by dc.Fill().
|
|
||||||
// The path is preserved after this operation.
|
|
||||||
func (dc *Context) ClipPreserve() {
|
|
||||||
clip := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
|
|
||||||
painter := raster.NewAlphaOverPainter(clip)
|
|
||||||
dc.fill(painter)
|
|
||||||
if dc.mask == nil {
|
|
||||||
dc.mask = clip
|
|
||||||
} else {
|
|
||||||
mask := image.NewAlpha(image.Rect(0, 0, dc.width, dc.height))
|
|
||||||
draw.DrawMask(mask, mask.Bounds(), clip, image.Point{}, dc.mask, image.Point{}, draw.Over)
|
|
||||||
dc.mask = mask
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMask allows you to directly set the *image.Alpha to be used as a clipping
|
|
||||||
// mask. It must be the same size as the context, else an error is returned
|
|
||||||
// and the mask is unchanged.
|
|
||||||
func (dc *Context) SetMask(mask *image.Alpha) error {
|
|
||||||
if mask.Bounds().Size() != dc.im.Bounds().Size() {
|
|
||||||
return errors.New("mask size must match context size")
|
|
||||||
}
|
|
||||||
dc.mask = mask
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// AsMask returns an *image.Alpha representing the alpha channel of this
|
|
||||||
// context. This can be useful for advanced clipping operations where you first
|
|
||||||
// render the mask geometry and then use it as a mask.
|
|
||||||
func (dc *Context) AsMask() *image.Alpha {
|
|
||||||
mask := image.NewAlpha(dc.im.Bounds())
|
|
||||||
draw.Draw(mask, dc.im.Bounds(), dc.im, image.Point{}, draw.Src)
|
|
||||||
return mask
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvertMask inverts the alpha values in the current clipping mask such that
|
|
||||||
// a fully transparent region becomes fully opaque and vice versa.
|
|
||||||
func (dc *Context) InvertMask() {
|
|
||||||
if dc.mask == nil {
|
|
||||||
dc.mask = image.NewAlpha(dc.im.Bounds())
|
|
||||||
} else {
|
|
||||||
for i, a := range dc.mask.Pix {
|
|
||||||
dc.mask.Pix[i] = 255 - a
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clip updates the clipping region by intersecting the current
|
|
||||||
// clipping region with the current path as it would be filled by dc.Fill().
|
|
||||||
// The path is cleared after this operation.
|
|
||||||
func (dc *Context) Clip() {
|
|
||||||
dc.ClipPreserve()
|
|
||||||
dc.ClearPath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResetClip clears the clipping region.
|
|
||||||
func (dc *Context) ResetClip() {
|
|
||||||
dc.mask = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convenient Drawing Functions
|
|
||||||
|
|
||||||
// Clear fills the entire image with the current color.
|
|
||||||
func (dc *Context) Clear() {
|
|
||||||
src := image.NewUniform(dc.color)
|
|
||||||
draw.Draw(dc.im, dc.im.Bounds(), src, image.Point{}, draw.Src)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPixel sets the color of the specified pixel using the current color.
|
|
||||||
func (dc *Context) SetPixel(x, y int) {
|
|
||||||
dc.im.Set(x, y, dc.color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawPoint is like DrawCircle but ensures that a circle of the specified
|
|
||||||
// size is drawn regardless of the current transformation matrix. The position
|
|
||||||
// is still transformed, but not the shape of the point.
|
|
||||||
func (dc *Context) DrawPoint(x, y, r float64) {
|
|
||||||
dc.Push()
|
|
||||||
tx, ty := dc.TransformPoint(x, y)
|
|
||||||
dc.Identity()
|
|
||||||
dc.DrawCircle(tx, ty, r)
|
|
||||||
dc.Pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawLine(x1, y1, x2, y2 float64) {
|
|
||||||
dc.MoveTo(x1, y1)
|
|
||||||
dc.LineTo(x2, y2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawRectangle(x, y, w, h float64) {
|
|
||||||
dc.NewSubPath()
|
|
||||||
dc.MoveTo(x, y)
|
|
||||||
dc.LineTo(x+w, y)
|
|
||||||
dc.LineTo(x+w, y+h)
|
|
||||||
dc.LineTo(x, y+h)
|
|
||||||
dc.ClosePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawRoundedRectangle(x, y, w, h, r float64) {
|
|
||||||
x0, x1, x2, x3 := x, x+r, x+w-r, x+w
|
|
||||||
y0, y1, y2, y3 := y, y+r, y+h-r, y+h
|
|
||||||
dc.NewSubPath()
|
|
||||||
dc.MoveTo(x1, y0)
|
|
||||||
dc.LineTo(x2, y0)
|
|
||||||
dc.DrawArc(x2, y1, r, Radians(270), Radians(360))
|
|
||||||
dc.LineTo(x3, y2)
|
|
||||||
dc.DrawArc(x2, y2, r, Radians(0), Radians(90))
|
|
||||||
dc.LineTo(x1, y3)
|
|
||||||
dc.DrawArc(x1, y2, r, Radians(90), Radians(180))
|
|
||||||
dc.LineTo(x0, y1)
|
|
||||||
dc.DrawArc(x1, y1, r, Radians(180), Radians(270))
|
|
||||||
dc.ClosePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawEllipticalArc(x, y, rx, ry, angle1, angle2 float64) {
|
|
||||||
const n = 16
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
p1 := float64(i+0) / n
|
|
||||||
p2 := float64(i+1) / n
|
|
||||||
a1 := angle1 + (angle2-angle1)*p1
|
|
||||||
a2 := angle1 + (angle2-angle1)*p2
|
|
||||||
x0 := x + rx*math.Cos(a1)
|
|
||||||
y0 := y + ry*math.Sin(a1)
|
|
||||||
x1 := x + rx*math.Cos((a1+a2)/2)
|
|
||||||
y1 := y + ry*math.Sin((a1+a2)/2)
|
|
||||||
x2 := x + rx*math.Cos(a2)
|
|
||||||
y2 := y + ry*math.Sin(a2)
|
|
||||||
cx := 2*x1 - x0/2 - x2/2
|
|
||||||
cy := 2*y1 - y0/2 - y2/2
|
|
||||||
if i == 0 {
|
|
||||||
if dc.hasCurrent {
|
|
||||||
dc.LineTo(x0, y0)
|
|
||||||
} else {
|
|
||||||
dc.MoveTo(x0, y0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dc.QuadraticTo(cx, cy, x2, y2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawEllipse(x, y, rx, ry float64) {
|
|
||||||
dc.NewSubPath()
|
|
||||||
dc.DrawEllipticalArc(x, y, rx, ry, 0, 2*math.Pi)
|
|
||||||
dc.ClosePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawArc(x, y, r, angle1, angle2 float64) {
|
|
||||||
dc.DrawEllipticalArc(x, y, r, r, angle1, angle2)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawCircle(x, y, r float64) {
|
|
||||||
dc.NewSubPath()
|
|
||||||
dc.DrawEllipticalArc(x, y, r, r, 0, 2*math.Pi)
|
|
||||||
dc.ClosePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) DrawRegularPolygon(n int, x, y, r, rotation float64) {
|
|
||||||
angle := 2 * math.Pi / float64(n)
|
|
||||||
rotation -= math.Pi / 2
|
|
||||||
if n%2 == 0 {
|
|
||||||
rotation += angle / 2
|
|
||||||
}
|
|
||||||
dc.NewSubPath()
|
|
||||||
for i := 0; i < n; i++ {
|
|
||||||
a := rotation + angle*float64(i)
|
|
||||||
dc.LineTo(x+r*math.Cos(a), y+r*math.Sin(a))
|
|
||||||
}
|
|
||||||
dc.ClosePath()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetInterpolator sets the current context's drawing interpolator.
|
|
||||||
func (dc *Context) SetInterpolator(interp draw.Interpolator) {
|
|
||||||
if interp == nil {
|
|
||||||
panic(errors.New("gg: invalid interpolator"))
|
|
||||||
}
|
|
||||||
dc.interp = interp
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawImage draws the specified image at the specified point.
|
|
||||||
func (dc *Context) DrawImage(im image.Image, x, y int) {
|
|
||||||
dc.DrawImageAnchored(im, x, y, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawImageAnchored draws the specified image at the specified anchor point.
|
|
||||||
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
|
|
||||||
// image. Use ax=0.5, ay=0.5 to center the image at the specified point.
|
|
||||||
func (dc *Context) DrawImageAnchored(im image.Image, x, y int, ax, ay float64) {
|
|
||||||
s := im.Bounds().Size()
|
|
||||||
x -= int(ax * float64(s.X))
|
|
||||||
y -= int(ay * float64(s.Y))
|
|
||||||
var (
|
|
||||||
fx = float64(x)
|
|
||||||
fy = float64(y)
|
|
||||||
m = dc.matrix.Translate(fx, fy)
|
|
||||||
s2d = f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
|
||||||
opt *draw.Options
|
|
||||||
)
|
|
||||||
if dc.mask != nil {
|
|
||||||
opt = &draw.Options{
|
|
||||||
DstMask: dc.mask,
|
|
||||||
DstMaskP: image.Point{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dc.interp.Transform(dc.im, s2d, im, im.Bounds(), draw.Over, opt)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Text Functions
|
|
||||||
|
|
||||||
func (dc *Context) SetFontFace(fontFace font.Face) {
|
|
||||||
dc.fontFace = fontFace
|
|
||||||
dc.fontHeight = float64(fontFace.Metrics().Height) / 64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) LoadFontFace(path string, points float64) error {
|
|
||||||
face, err := LoadFontFace(path, points)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.fontFace = face
|
|
||||||
dc.fontHeight = points * 72 / 96
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) LoadFontFaceFromFS(fsys fs.FS, path string, points float64) error {
|
|
||||||
face, err := LoadFontFaceFromFS(fsys, path, points)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.fontFace = face
|
|
||||||
dc.fontHeight = points * 72 / 96
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) LoadFontFaceFromBytes(raw []byte, points float64) error {
|
|
||||||
face, err := LoadFontFaceFromBytes(raw, points)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
dc.fontFace = face
|
|
||||||
dc.fontHeight = points * 72 / 96
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) FontHeight() float64 {
|
|
||||||
return dc.fontHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) drawString(im *image.RGBA, s string, x, y float64) {
|
|
||||||
d := &font.Drawer{
|
|
||||||
Dst: im,
|
|
||||||
Src: image.NewUniform(dc.color),
|
|
||||||
Face: dc.fontFace,
|
|
||||||
Dot: fixp(x, y),
|
|
||||||
}
|
|
||||||
// based on Drawer.DrawString() in golang.org/x/image/font/font.go
|
|
||||||
prevC := rune(-1)
|
|
||||||
for _, c := range s {
|
|
||||||
if prevC >= 0 {
|
|
||||||
d.Dot.X += d.Face.Kern(prevC, c)
|
|
||||||
}
|
|
||||||
dr, mask, maskp, advance, ok := d.Face.Glyph(d.Dot, c)
|
|
||||||
if !ok {
|
|
||||||
// TODO: is falling back on the U+FFFD glyph the responsibility of
|
|
||||||
// the Drawer or the Face?
|
|
||||||
// TODO: set prevC = '\ufffd'?
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
sr := dr.Sub(dr.Min)
|
|
||||||
fx, fy := float64(dr.Min.X), float64(dr.Min.Y)
|
|
||||||
m := dc.matrix.Translate(fx, fy)
|
|
||||||
s2d := f64.Aff3{m.XX, m.XY, m.X0, m.YX, m.YY, m.Y0}
|
|
||||||
dc.interp.Transform(d.Dst, s2d, d.Src, sr, draw.Over, &draw.Options{
|
|
||||||
SrcMask: mask,
|
|
||||||
SrcMaskP: maskp,
|
|
||||||
})
|
|
||||||
d.Dot.X += advance
|
|
||||||
prevC = c
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawString draws the specified text at the specified point.
|
|
||||||
func (dc *Context) DrawString(s string, x, y float64) {
|
|
||||||
dc.DrawStringAnchored(s, x, y, 0, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawStringAnchored draws the specified text at the specified anchor point.
|
|
||||||
// The anchor point is x - w * ax, y - h * ay, where w, h is the size of the
|
|
||||||
// text. Use ax=0.5, ay=0.5 to center the text at the specified point.
|
|
||||||
func (dc *Context) DrawStringAnchored(s string, x, y, ax, ay float64) {
|
|
||||||
w, h := dc.MeasureString(s)
|
|
||||||
x -= ax * w
|
|
||||||
y += ay * h
|
|
||||||
if dc.mask == nil {
|
|
||||||
dc.drawString(dc.im, s, x, y)
|
|
||||||
} else {
|
|
||||||
im := image.NewRGBA(image.Rect(0, 0, dc.width, dc.height))
|
|
||||||
dc.drawString(im, s, x, y)
|
|
||||||
draw.DrawMask(dc.im, dc.im.Bounds(), im, image.Point{}, dc.mask, image.Point{}, draw.Over)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DrawStringWrapped word-wraps the specified string to the given max width
|
|
||||||
// and then draws it at the specified anchor point using the given line
|
|
||||||
// spacing and text alignment.
|
|
||||||
func (dc *Context) DrawStringWrapped(s string, x, y, ax, ay, width, lineSpacing float64, align Align) {
|
|
||||||
lines := dc.WordWrap(s, width)
|
|
||||||
|
|
||||||
// sync h formula with MeasureMultilineString
|
|
||||||
h := float64(len(lines)) * dc.fontHeight * lineSpacing
|
|
||||||
h -= (lineSpacing - 1) * dc.fontHeight
|
|
||||||
|
|
||||||
x -= ax * width
|
|
||||||
y -= ay * h
|
|
||||||
switch align {
|
|
||||||
case AlignLeft:
|
|
||||||
ax = 0
|
|
||||||
case AlignCenter:
|
|
||||||
ax = 0.5
|
|
||||||
x += width / 2
|
|
||||||
case AlignRight:
|
|
||||||
ax = 1
|
|
||||||
x += width
|
|
||||||
}
|
|
||||||
ay = 1
|
|
||||||
for _, line := range lines {
|
|
||||||
dc.DrawStringAnchored(line, x, y, ax, ay)
|
|
||||||
y += dc.fontHeight * lineSpacing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dc *Context) MeasureMultilineString(s string, lineSpacing float64) (width, height float64) {
|
|
||||||
lines := strings.Split(s, "\n")
|
|
||||||
|
|
||||||
// sync h formula with DrawStringWrapped
|
|
||||||
height = float64(len(lines)) * dc.fontHeight * lineSpacing
|
|
||||||
height -= (lineSpacing - 1) * dc.fontHeight
|
|
||||||
|
|
||||||
d := &font.Drawer{
|
|
||||||
Face: dc.fontFace,
|
|
||||||
}
|
|
||||||
|
|
||||||
// max width from lines
|
|
||||||
for _, line := range lines {
|
|
||||||
adv := d.MeasureString(line)
|
|
||||||
currentWidth := float64(adv >> 6) // from gg.Context.MeasureString
|
|
||||||
if currentWidth > width {
|
|
||||||
width = currentWidth
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return width, height
|
|
||||||
}
|
|
||||||
|
|
||||||
// MeasureString returns the rendered width and height of the specified text
|
|
||||||
// given the current font face.
|
|
||||||
func (dc *Context) MeasureString(s string) (w, h float64) {
|
|
||||||
d := &font.Drawer{
|
|
||||||
Face: dc.fontFace,
|
|
||||||
}
|
|
||||||
a := d.MeasureString(s)
|
|
||||||
return float64(a >> 6), dc.fontHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// WordWrap wraps the specified string to the given max width and current
|
|
||||||
// font face.
|
|
||||||
func (dc *Context) WordWrap(s string, w float64) []string {
|
|
||||||
return wordWrap(dc, s, w)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Transformation Matrix Operations
|
|
||||||
|
|
||||||
// Identity resets the current transformation matrix to the identity matrix.
|
|
||||||
// This results in no translating, scaling, rotating, or shearing.
|
|
||||||
func (dc *Context) Identity() {
|
|
||||||
dc.matrix = Identity()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate updates the current matrix with a translation.
|
|
||||||
func (dc *Context) Translate(x, y float64) {
|
|
||||||
dc.matrix = dc.matrix.Translate(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale updates the current matrix with a scaling factor.
|
|
||||||
// Scaling occurs about the origin.
|
|
||||||
func (dc *Context) Scale(x, y float64) {
|
|
||||||
dc.matrix = dc.matrix.Scale(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ScaleAbout updates the current matrix with a scaling factor.
|
|
||||||
// Scaling occurs about the specified point.
|
|
||||||
func (dc *Context) ScaleAbout(sx, sy, x, y float64) {
|
|
||||||
dc.Translate(x, y)
|
|
||||||
dc.Scale(sx, sy)
|
|
||||||
dc.Translate(-x, -y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate updates the current matrix with a anticlockwise rotation.
|
|
||||||
// Rotation occurs about the origin. Angle is specified in radians.
|
|
||||||
func (dc *Context) Rotate(angle float64) {
|
|
||||||
dc.matrix = dc.matrix.Rotate(angle)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RotateAbout updates the current matrix with a anticlockwise rotation.
|
|
||||||
// Rotation occurs about the specified point. Angle is specified in radians.
|
|
||||||
func (dc *Context) RotateAbout(angle, x, y float64) {
|
|
||||||
dc.Translate(x, y)
|
|
||||||
dc.Rotate(angle)
|
|
||||||
dc.Translate(-x, -y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shear updates the current matrix with a shearing angle.
|
|
||||||
// Shearing occurs about the origin.
|
|
||||||
func (dc *Context) Shear(x, y float64) {
|
|
||||||
dc.matrix = dc.matrix.Shear(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShearAbout updates the current matrix with a shearing angle.
|
|
||||||
// Shearing occurs about the specified point.
|
|
||||||
func (dc *Context) ShearAbout(sx, sy, x, y float64) {
|
|
||||||
dc.Translate(x, y)
|
|
||||||
dc.Shear(sx, sy)
|
|
||||||
dc.Translate(-x, -y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransformPoint multiplies the specified point by the current matrix,
|
|
||||||
// returning a transformed position.
|
|
||||||
func (dc *Context) TransformPoint(x, y float64) (tx, ty float64) {
|
|
||||||
return dc.matrix.TransformPoint(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
// InvertY flips the Y axis so that Y grows from bottom to top and Y=0 is at
|
|
||||||
// the bottom of the image.
|
|
||||||
func (dc *Context) InvertY() {
|
|
||||||
dc.Translate(0, float64(dc.height))
|
|
||||||
dc.Scale(1, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stack
|
|
||||||
|
|
||||||
// Push saves the current state of the context for later retrieval. These
|
|
||||||
// can be nested.
|
|
||||||
func (dc *Context) Push() {
|
|
||||||
x := *dc
|
|
||||||
dc.stack = append(dc.stack, &x)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pop restores the last saved context state from the stack.
|
|
||||||
func (dc *Context) Pop() {
|
|
||||||
var (
|
|
||||||
before = *dc
|
|
||||||
s = dc.stack
|
|
||||||
ctx *Context
|
|
||||||
)
|
|
||||||
ctx, dc.stack = s[len(s)-1], s[:len(s)-1]
|
|
||||||
*dc = *ctx
|
|
||||||
dc.mask = before.mask
|
|
||||||
dc.strokePath = before.strokePath
|
|
||||||
dc.fillPath = before.fillPath
|
|
||||||
dc.start = before.start
|
|
||||||
dc.current = before.current
|
|
||||||
dc.hasCurrent = before.hasCurrent
|
|
||||||
}
|
|
||||||
8
vendor/git.sr.ht/~sbinet/gg/gg.go
vendored
8
vendor/git.sr.ht/~sbinet/gg/gg.go
vendored
@@ -1,8 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:generate go run github.com/campoy/embedmd -w README.md
|
|
||||||
|
|
||||||
// Package gg provides a simple API for rendering 2D graphics in pure Go.
|
|
||||||
package gg // import "git.sr.ht/~sbinet/gg"
|
|
||||||
11
vendor/git.sr.ht/~sbinet/gg/gg_gen.go
vendored
11
vendor/git.sr.ht/~sbinet/gg/gg_gen.go
vendored
@@ -1,11 +0,0 @@
|
|||||||
// Copyright ©2023 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
//go:build tools
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
_ "github.com/campoy/embedmd" // needed for generating gallery
|
|
||||||
)
|
|
||||||
252
vendor/git.sr.ht/~sbinet/gg/gradient.go
vendored
252
vendor/git.sr.ht/~sbinet/gg/gradient.go
vendored
@@ -1,252 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image/color"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type stop struct {
|
|
||||||
pos float64
|
|
||||||
color color.Color
|
|
||||||
}
|
|
||||||
|
|
||||||
type stops []stop
|
|
||||||
|
|
||||||
// Len satisfies the Sort interface.
|
|
||||||
func (s stops) Len() int {
|
|
||||||
return len(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Less satisfies the Sort interface.
|
|
||||||
func (s stops) Less(i, j int) bool {
|
|
||||||
return s[i].pos < s[j].pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// Swap satisfies the Sort interface.
|
|
||||||
func (s stops) Swap(i, j int) {
|
|
||||||
s[i], s[j] = s[j], s[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
type Gradient interface {
|
|
||||||
Pattern
|
|
||||||
AddColorStop(offset float64, color color.Color)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Linear Gradient
|
|
||||||
type linearGradient struct {
|
|
||||||
x0, y0, x1, y1 float64
|
|
||||||
stops stops
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *linearGradient) ColorAt(x, y int) color.Color {
|
|
||||||
if len(g.stops) == 0 {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
fx, fy := float64(x), float64(y)
|
|
||||||
x0, y0, x1, y1 := g.x0, g.y0, g.x1, g.y1
|
|
||||||
dx, dy := x1-x0, y1-y0
|
|
||||||
|
|
||||||
// Horizontal
|
|
||||||
if dy == 0 && dx != 0 {
|
|
||||||
return getColor((fx-x0)/dx, g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vertical
|
|
||||||
if dx == 0 && dy != 0 {
|
|
||||||
return getColor((fy-y0)/dy, g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dot product
|
|
||||||
s0 := dx*(fx-x0) + dy*(fy-y0)
|
|
||||||
if s0 < 0 {
|
|
||||||
return g.stops[0].color
|
|
||||||
}
|
|
||||||
// Calculate distance to (x0,y0) alone (x0,y0)->(x1,y1)
|
|
||||||
mag := math.Hypot(dx, dy)
|
|
||||||
u := ((fx-x0)*-dy + (fy-y0)*dx) / (mag * mag)
|
|
||||||
x2, y2 := x0+u*-dy, y0+u*dx
|
|
||||||
d := math.Hypot(fx-x2, fy-y2) / mag
|
|
||||||
return getColor(d, g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *linearGradient) AddColorStop(offset float64, color color.Color) {
|
|
||||||
g.stops = append(g.stops, stop{pos: offset, color: color})
|
|
||||||
sort.Sort(g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLinearGradient(x0, y0, x1, y1 float64) Gradient {
|
|
||||||
g := &linearGradient{
|
|
||||||
x0: x0, y0: y0,
|
|
||||||
x1: x1, y1: y1,
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
// Radial Gradient
|
|
||||||
type circle struct {
|
|
||||||
x, y, r float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type radialGradient struct {
|
|
||||||
c0, c1, cd circle
|
|
||||||
a, inva float64
|
|
||||||
mindr float64
|
|
||||||
stops stops
|
|
||||||
}
|
|
||||||
|
|
||||||
func dot3(x0, y0, z0, x1, y1, z1 float64) float64 {
|
|
||||||
return x0*x1 + y0*y1 + z0*z1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *radialGradient) ColorAt(x, y int) color.Color {
|
|
||||||
if len(g.stops) == 0 {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy from pixman's pixman-radial-gradient.c
|
|
||||||
|
|
||||||
dx, dy := float64(x)+0.5-g.c0.x, float64(y)+0.5-g.c0.y
|
|
||||||
b := dot3(dx, dy, g.c0.r, g.cd.x, g.cd.y, g.cd.r)
|
|
||||||
c := dot3(dx, dy, -g.c0.r, dx, dy, g.c0.r)
|
|
||||||
|
|
||||||
if g.a == 0 {
|
|
||||||
if b == 0 {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
t := 0.5 * c / b
|
|
||||||
if t*g.cd.r >= g.mindr {
|
|
||||||
return getColor(t, g.stops)
|
|
||||||
}
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
discr := dot3(b, g.a, 0, b, -c, 0)
|
|
||||||
if discr >= 0 {
|
|
||||||
sqrtdiscr := math.Sqrt(discr)
|
|
||||||
t0 := (b + sqrtdiscr) * g.inva
|
|
||||||
t1 := (b - sqrtdiscr) * g.inva
|
|
||||||
|
|
||||||
if t0*g.cd.r >= g.mindr {
|
|
||||||
return getColor(t0, g.stops)
|
|
||||||
} else if t1*g.cd.r >= g.mindr {
|
|
||||||
return getColor(t1, g.stops)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *radialGradient) AddColorStop(offset float64, color color.Color) {
|
|
||||||
g.stops = append(g.stops, stop{pos: offset, color: color})
|
|
||||||
sort.Sort(g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRadialGradient(x0, y0, r0, x1, y1, r1 float64) Gradient {
|
|
||||||
c0 := circle{x0, y0, r0}
|
|
||||||
c1 := circle{x1, y1, r1}
|
|
||||||
cd := circle{x1 - x0, y1 - y0, r1 - r0}
|
|
||||||
a := dot3(cd.x, cd.y, -cd.r, cd.x, cd.y, cd.r)
|
|
||||||
var inva float64
|
|
||||||
if a != 0 {
|
|
||||||
inva = 1.0 / a
|
|
||||||
}
|
|
||||||
mindr := -c0.r
|
|
||||||
g := &radialGradient{
|
|
||||||
c0: c0,
|
|
||||||
c1: c1,
|
|
||||||
cd: cd,
|
|
||||||
a: a,
|
|
||||||
inva: inva,
|
|
||||||
mindr: mindr,
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
// Conic Gradient
|
|
||||||
type conicGradient struct {
|
|
||||||
cx, cy float64
|
|
||||||
rotation float64
|
|
||||||
stops stops
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *conicGradient) ColorAt(x, y int) color.Color {
|
|
||||||
if len(g.stops) == 0 {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
a := math.Atan2(float64(y)-g.cy, float64(x)-g.cx)
|
|
||||||
t := norm(a, -math.Pi, math.Pi) - g.rotation
|
|
||||||
if t < 0 {
|
|
||||||
t += 1
|
|
||||||
}
|
|
||||||
return getColor(t, g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *conicGradient) AddColorStop(offset float64, color color.Color) {
|
|
||||||
g.stops = append(g.stops, stop{pos: offset, color: color})
|
|
||||||
sort.Sort(g.stops)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConicGradient(cx, cy, deg float64) Gradient {
|
|
||||||
g := &conicGradient{
|
|
||||||
cx: cx,
|
|
||||||
cy: cy,
|
|
||||||
rotation: normalizeAngle(deg) / 360,
|
|
||||||
}
|
|
||||||
return g
|
|
||||||
}
|
|
||||||
|
|
||||||
func normalizeAngle(t float64) float64 {
|
|
||||||
t = math.Mod(t, 360)
|
|
||||||
if t < 0 {
|
|
||||||
t += 360
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Map value which is in range [a..b] to range [0..1]
|
|
||||||
func norm(value, a, b float64) float64 {
|
|
||||||
return (value - a) * (1.0 / (b - a))
|
|
||||||
}
|
|
||||||
|
|
||||||
func getColor(pos float64, stops stops) color.Color {
|
|
||||||
if pos <= 0.0 || len(stops) == 1 {
|
|
||||||
return stops[0].color
|
|
||||||
}
|
|
||||||
|
|
||||||
last := stops[len(stops)-1]
|
|
||||||
|
|
||||||
if pos >= last.pos {
|
|
||||||
return last.color
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, stop := range stops[1:] {
|
|
||||||
if pos < stop.pos {
|
|
||||||
pos = (pos - stops[i].pos) / (stop.pos - stops[i].pos)
|
|
||||||
return colorLerp(stops[i].color, stop.color, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return last.color
|
|
||||||
}
|
|
||||||
|
|
||||||
func colorLerp(c0, c1 color.Color, t float64) color.Color {
|
|
||||||
r0, g0, b0, a0 := c0.RGBA()
|
|
||||||
r1, g1, b1, a1 := c1.RGBA()
|
|
||||||
|
|
||||||
return color.RGBA{
|
|
||||||
lerp(r0, r1, t),
|
|
||||||
lerp(g0, g1, t),
|
|
||||||
lerp(b0, b1, t),
|
|
||||||
lerp(a0, a1, t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lerp(a, b uint32, t float64) uint8 {
|
|
||||||
return uint8(int32(float64(a)*(1.0-t)+float64(b)*t) >> 8)
|
|
||||||
}
|
|
||||||
92
vendor/git.sr.ht/~sbinet/gg/matrix.go
vendored
92
vendor/git.sr.ht/~sbinet/gg/matrix.go
vendored
@@ -1,92 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import "math"
|
|
||||||
|
|
||||||
type Matrix struct {
|
|
||||||
XX, YX, XY, YY, X0, Y0 float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func Identity() Matrix {
|
|
||||||
return Matrix{
|
|
||||||
1, 0,
|
|
||||||
0, 1,
|
|
||||||
0, 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Translate(x, y float64) Matrix {
|
|
||||||
return Matrix{
|
|
||||||
1, 0,
|
|
||||||
0, 1,
|
|
||||||
x, y,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Scale(x, y float64) Matrix {
|
|
||||||
return Matrix{
|
|
||||||
x, 0,
|
|
||||||
0, y,
|
|
||||||
0, 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Rotate(angle float64) Matrix {
|
|
||||||
c := math.Cos(angle)
|
|
||||||
s := math.Sin(angle)
|
|
||||||
return Matrix{
|
|
||||||
c, s,
|
|
||||||
-s, c,
|
|
||||||
0, 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Shear(x, y float64) Matrix {
|
|
||||||
return Matrix{
|
|
||||||
1, y,
|
|
||||||
x, 1,
|
|
||||||
0, 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) Multiply(b Matrix) Matrix {
|
|
||||||
return Matrix{
|
|
||||||
a.XX*b.XX + a.YX*b.XY,
|
|
||||||
a.XX*b.YX + a.YX*b.YY,
|
|
||||||
a.XY*b.XX + a.YY*b.XY,
|
|
||||||
a.XY*b.YX + a.YY*b.YY,
|
|
||||||
a.X0*b.XX + a.Y0*b.XY + b.X0,
|
|
||||||
a.X0*b.YX + a.Y0*b.YY + b.Y0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) TransformVector(x, y float64) (tx, ty float64) {
|
|
||||||
tx = a.XX*x + a.XY*y
|
|
||||||
ty = a.YX*x + a.YY*y
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) TransformPoint(x, y float64) (tx, ty float64) {
|
|
||||||
tx = a.XX*x + a.XY*y + a.X0
|
|
||||||
ty = a.YX*x + a.YY*y + a.Y0
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) Translate(x, y float64) Matrix {
|
|
||||||
return Translate(x, y).Multiply(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) Scale(x, y float64) Matrix {
|
|
||||||
return Scale(x, y).Multiply(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) Rotate(angle float64) Matrix {
|
|
||||||
return Rotate(angle).Multiply(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Matrix) Shear(x, y float64) Matrix {
|
|
||||||
return Shear(x, y).Multiply(a)
|
|
||||||
}
|
|
||||||
167
vendor/git.sr.ht/~sbinet/gg/path.go
vendored
167
vendor/git.sr.ht/~sbinet/gg/path.go
vendored
@@ -1,167 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/raster"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func flattenPath(p raster.Path) [][]Point {
|
|
||||||
var result [][]Point
|
|
||||||
var path []Point
|
|
||||||
var cx, cy float64
|
|
||||||
for i := 0; i < len(p); {
|
|
||||||
switch p[i] {
|
|
||||||
case 0:
|
|
||||||
if len(path) > 0 {
|
|
||||||
result = append(result, path)
|
|
||||||
path = nil
|
|
||||||
}
|
|
||||||
x := unfix(p[i+1])
|
|
||||||
y := unfix(p[i+2])
|
|
||||||
path = append(path, Point{x, y})
|
|
||||||
cx, cy = x, y
|
|
||||||
i += 4
|
|
||||||
case 1:
|
|
||||||
x := unfix(p[i+1])
|
|
||||||
y := unfix(p[i+2])
|
|
||||||
path = append(path, Point{x, y})
|
|
||||||
cx, cy = x, y
|
|
||||||
i += 4
|
|
||||||
case 2:
|
|
||||||
x1 := unfix(p[i+1])
|
|
||||||
y1 := unfix(p[i+2])
|
|
||||||
x2 := unfix(p[i+3])
|
|
||||||
y2 := unfix(p[i+4])
|
|
||||||
points := QuadraticBezier(cx, cy, x1, y1, x2, y2)
|
|
||||||
path = append(path, points...)
|
|
||||||
cx, cy = x2, y2
|
|
||||||
i += 6
|
|
||||||
case 3:
|
|
||||||
x1 := unfix(p[i+1])
|
|
||||||
y1 := unfix(p[i+2])
|
|
||||||
x2 := unfix(p[i+3])
|
|
||||||
y2 := unfix(p[i+4])
|
|
||||||
x3 := unfix(p[i+5])
|
|
||||||
y3 := unfix(p[i+6])
|
|
||||||
points := CubicBezier(cx, cy, x1, y1, x2, y2, x3, y3)
|
|
||||||
path = append(path, points...)
|
|
||||||
cx, cy = x3, y3
|
|
||||||
i += 8
|
|
||||||
default:
|
|
||||||
panic("bad path")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(path) > 0 {
|
|
||||||
result = append(result, path)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func dashPath(paths [][]Point, dashes []float64, offset float64) [][]Point {
|
|
||||||
var result [][]Point
|
|
||||||
if len(dashes) == 0 {
|
|
||||||
return paths
|
|
||||||
}
|
|
||||||
if len(dashes) == 1 {
|
|
||||||
dashes = append(dashes, dashes[0])
|
|
||||||
}
|
|
||||||
for _, path := range paths {
|
|
||||||
if len(path) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
previous := path[0]
|
|
||||||
pathIndex := 1
|
|
||||||
dashIndex := 0
|
|
||||||
segmentLength := 0.0
|
|
||||||
|
|
||||||
// offset
|
|
||||||
if offset != 0 {
|
|
||||||
var totalLength float64
|
|
||||||
for _, dashLength := range dashes {
|
|
||||||
totalLength += dashLength
|
|
||||||
}
|
|
||||||
offset = math.Mod(offset, totalLength)
|
|
||||||
if offset < 0 {
|
|
||||||
offset += totalLength
|
|
||||||
}
|
|
||||||
for i, dashLength := range dashes {
|
|
||||||
offset -= dashLength
|
|
||||||
if offset < 0 {
|
|
||||||
dashIndex = i
|
|
||||||
segmentLength = dashLength + offset
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var segment []Point
|
|
||||||
segment = append(segment, previous)
|
|
||||||
for pathIndex < len(path) {
|
|
||||||
dashLength := dashes[dashIndex]
|
|
||||||
point := path[pathIndex]
|
|
||||||
d := previous.Distance(point)
|
|
||||||
maxd := dashLength - segmentLength
|
|
||||||
if d > maxd {
|
|
||||||
t := maxd / d
|
|
||||||
p := previous.Interpolate(point, t)
|
|
||||||
segment = append(segment, p)
|
|
||||||
if dashIndex%2 == 0 && len(segment) > 1 {
|
|
||||||
result = append(result, segment)
|
|
||||||
}
|
|
||||||
segment = nil
|
|
||||||
segment = append(segment, p)
|
|
||||||
segmentLength = 0
|
|
||||||
previous = p
|
|
||||||
dashIndex = (dashIndex + 1) % len(dashes)
|
|
||||||
} else {
|
|
||||||
segment = append(segment, point)
|
|
||||||
previous = point
|
|
||||||
segmentLength += d
|
|
||||||
pathIndex++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if dashIndex%2 == 0 && len(segment) > 1 {
|
|
||||||
result = append(result, segment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func rasterPath(paths [][]Point) raster.Path {
|
|
||||||
var result raster.Path
|
|
||||||
for _, path := range paths {
|
|
||||||
var previous fixed.Point26_6
|
|
||||||
for i, point := range path {
|
|
||||||
f := point.Fixed()
|
|
||||||
if i == 0 {
|
|
||||||
result.Start(f)
|
|
||||||
} else {
|
|
||||||
dx := f.X - previous.X
|
|
||||||
dy := f.Y - previous.Y
|
|
||||||
if dx < 0 {
|
|
||||||
dx = -dx
|
|
||||||
}
|
|
||||||
if dy < 0 {
|
|
||||||
dy = -dy
|
|
||||||
}
|
|
||||||
if dx+dy > 4 {
|
|
||||||
// TODO: this is a hack for cases where two points are
|
|
||||||
// too close - causes rendering issues with joins / caps
|
|
||||||
result.Add1(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
previous = f
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func dashed(path raster.Path, dashes []float64, offset float64) raster.Path {
|
|
||||||
return rasterPath(dashPath(flattenPath(path), dashes, offset))
|
|
||||||
}
|
|
||||||
127
vendor/git.sr.ht/~sbinet/gg/pattern.go
vendored
127
vendor/git.sr.ht/~sbinet/gg/pattern.go
vendored
@@ -1,127 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"image"
|
|
||||||
"image/color"
|
|
||||||
|
|
||||||
"github.com/golang/freetype/raster"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RepeatOp int
|
|
||||||
|
|
||||||
const (
|
|
||||||
RepeatBoth RepeatOp = iota
|
|
||||||
RepeatX
|
|
||||||
RepeatY
|
|
||||||
RepeatNone
|
|
||||||
)
|
|
||||||
|
|
||||||
type Pattern interface {
|
|
||||||
ColorAt(x, y int) color.Color
|
|
||||||
}
|
|
||||||
|
|
||||||
// Solid Pattern
|
|
||||||
type solidPattern struct {
|
|
||||||
color color.Color
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *solidPattern) ColorAt(x, y int) color.Color {
|
|
||||||
return p.color
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSolidPattern(color color.Color) Pattern {
|
|
||||||
return &solidPattern{color: color}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Surface Pattern
|
|
||||||
type surfacePattern struct {
|
|
||||||
im image.Image
|
|
||||||
op RepeatOp
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *surfacePattern) ColorAt(x, y int) color.Color {
|
|
||||||
b := p.im.Bounds()
|
|
||||||
switch p.op {
|
|
||||||
case RepeatX:
|
|
||||||
if y >= b.Dy() {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
case RepeatY:
|
|
||||||
if x >= b.Dx() {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
case RepeatNone:
|
|
||||||
if x >= b.Dx() || y >= b.Dy() {
|
|
||||||
return color.Transparent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x = x%b.Dx() + b.Min.X
|
|
||||||
y = y%b.Dy() + b.Min.Y
|
|
||||||
return p.im.At(x, y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSurfacePattern(im image.Image, op RepeatOp) Pattern {
|
|
||||||
return &surfacePattern{im: im, op: op}
|
|
||||||
}
|
|
||||||
|
|
||||||
type patternPainter struct {
|
|
||||||
im *image.RGBA
|
|
||||||
mask *image.Alpha
|
|
||||||
p Pattern
|
|
||||||
}
|
|
||||||
|
|
||||||
// Paint satisfies the Painter interface.
|
|
||||||
func (r *patternPainter) Paint(ss []raster.Span, done bool) {
|
|
||||||
b := r.im.Bounds()
|
|
||||||
for _, s := range ss {
|
|
||||||
if s.Y < b.Min.Y {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if s.Y >= b.Max.Y {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.X0 < b.Min.X {
|
|
||||||
s.X0 = b.Min.X
|
|
||||||
}
|
|
||||||
if s.X1 > b.Max.X {
|
|
||||||
s.X1 = b.Max.X
|
|
||||||
}
|
|
||||||
if s.X0 >= s.X1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const m = 1<<16 - 1
|
|
||||||
y := s.Y - r.im.Rect.Min.Y
|
|
||||||
x0 := s.X0 - r.im.Rect.Min.X
|
|
||||||
// RGBAPainter.Paint() in $GOPATH/src/github.com/golang/freetype/raster/paint.go
|
|
||||||
i0 := (s.Y-r.im.Rect.Min.Y)*r.im.Stride + (s.X0-r.im.Rect.Min.X)*4
|
|
||||||
i1 := i0 + (s.X1-s.X0)*4
|
|
||||||
for i, x := i0, x0; i < i1; i, x = i+4, x+1 {
|
|
||||||
ma := s.Alpha
|
|
||||||
if r.mask != nil {
|
|
||||||
ma = ma * uint32(r.mask.AlphaAt(x, y).A) / 255
|
|
||||||
if ma == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c := r.p.ColorAt(x, y)
|
|
||||||
cr, cg, cb, ca := c.RGBA()
|
|
||||||
dr := uint32(r.im.Pix[i+0])
|
|
||||||
dg := uint32(r.im.Pix[i+1])
|
|
||||||
db := uint32(r.im.Pix[i+2])
|
|
||||||
da := uint32(r.im.Pix[i+3])
|
|
||||||
a := (m - (ca * ma / m)) * 0x101
|
|
||||||
r.im.Pix[i+0] = uint8((dr*a + cr*ma) / m >> 8)
|
|
||||||
r.im.Pix[i+1] = uint8((dg*a + cg*ma) / m >> 8)
|
|
||||||
r.im.Pix[i+2] = uint8((db*a + cb*ma) / m >> 8)
|
|
||||||
r.im.Pix[i+3] = uint8((da*a + ca*ma) / m >> 8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPatternPainter(im *image.RGBA, mask *image.Alpha, p Pattern) *patternPainter {
|
|
||||||
return &patternPainter{im, mask, p}
|
|
||||||
}
|
|
||||||
29
vendor/git.sr.ht/~sbinet/gg/point.go
vendored
29
vendor/git.sr.ht/~sbinet/gg/point.go
vendored
@@ -1,29 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Point struct {
|
|
||||||
X, Y float64
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Point) Fixed() fixed.Point26_6 {
|
|
||||||
return fixp(a.X, a.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Point) Distance(b Point) float64 {
|
|
||||||
return math.Hypot(a.X-b.X, a.Y-b.Y)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Point) Interpolate(b Point, t float64) Point {
|
|
||||||
x := a.X + (b.X-a.X)*t
|
|
||||||
y := a.Y + (b.Y-a.Y)*t
|
|
||||||
return Point{x, y}
|
|
||||||
}
|
|
||||||
203
vendor/git.sr.ht/~sbinet/gg/util.go
vendored
203
vendor/git.sr.ht/~sbinet/gg/util.go
vendored
@@ -1,203 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"image"
|
|
||||||
"image/draw"
|
|
||||||
"image/jpeg"
|
|
||||||
"image/png"
|
|
||||||
"io/fs"
|
|
||||||
"math"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/image/font"
|
|
||||||
"golang.org/x/image/font/opentype"
|
|
||||||
"golang.org/x/image/math/fixed"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Radians(degrees float64) float64 {
|
|
||||||
return degrees * math.Pi / 180
|
|
||||||
}
|
|
||||||
|
|
||||||
func Degrees(radians float64) float64 {
|
|
||||||
return radians * 180 / math.Pi
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadImage(path string) (image.Image, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
im, _, err := image.Decode(file)
|
|
||||||
return im, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadPNG(path string) (image.Image, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
return png.Decode(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SavePNG(path string, im image.Image) error {
|
|
||||||
file, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
err = png.Encode(file, im)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not encode PNG to %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadJPG(path string) (image.Image, error) {
|
|
||||||
file, err := os.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
return jpeg.Decode(file)
|
|
||||||
}
|
|
||||||
|
|
||||||
func SaveJPG(path string, im image.Image, quality int) error {
|
|
||||||
file, err := os.Create(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var opt jpeg.Options
|
|
||||||
opt.Quality = quality
|
|
||||||
|
|
||||||
err = jpeg.Encode(file, im, &opt)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not encode JPG to %q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func imageToRGBA(src image.Image) *image.RGBA {
|
|
||||||
bounds := src.Bounds()
|
|
||||||
dst := image.NewRGBA(bounds)
|
|
||||||
draw.Draw(dst, bounds, src, bounds.Min, draw.Src)
|
|
||||||
return dst
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseHexColor(x string) (r, g, b, a int) {
|
|
||||||
x = strings.TrimPrefix(x, "#")
|
|
||||||
a = 255
|
|
||||||
if len(x) == 3 {
|
|
||||||
format := "%1x%1x%1x"
|
|
||||||
fmt.Sscanf(x, format, &r, &g, &b)
|
|
||||||
r |= r << 4
|
|
||||||
g |= g << 4
|
|
||||||
b |= b << 4
|
|
||||||
}
|
|
||||||
if len(x) == 6 {
|
|
||||||
format := "%02x%02x%02x"
|
|
||||||
fmt.Sscanf(x, format, &r, &g, &b)
|
|
||||||
}
|
|
||||||
if len(x) == 8 {
|
|
||||||
format := "%02x%02x%02x%02x"
|
|
||||||
fmt.Sscanf(x, format, &r, &g, &b, &a)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func fixp(x, y float64) fixed.Point26_6 {
|
|
||||||
return fixed.Point26_6{fix(x), fix(y)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func fix(x float64) fixed.Int26_6 {
|
|
||||||
return fixed.Int26_6(math.Round(x * 64))
|
|
||||||
}
|
|
||||||
|
|
||||||
func unfix(x fixed.Int26_6) float64 {
|
|
||||||
const shift, mask = 6, 1<<6 - 1
|
|
||||||
if x >= 0 {
|
|
||||||
return float64(x>>shift) + float64(x&mask)/64
|
|
||||||
}
|
|
||||||
x = -x
|
|
||||||
if x >= 0 {
|
|
||||||
return -(float64(x>>shift) + float64(x&mask)/64)
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFontFace is a helper function to load the specified font file with
|
|
||||||
// the specified point size. Note that the returned `font.Face` objects
|
|
||||||
// are not thread safe and cannot be used in parallel across goroutines.
|
|
||||||
// You can usually just use the Context.LoadFontFace function instead of
|
|
||||||
// this package-level function.
|
|
||||||
func LoadFontFace(path string, points float64) (font.Face, error) {
|
|
||||||
fontBytes, err := os.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return LoadFontFaceFromBytes(fontBytes, points)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFontFaceFromFS is a helper function to load the specified font file from
|
|
||||||
// the provided filesystem and path, with the specified point size.
|
|
||||||
//
|
|
||||||
// Note that the returned `font.Face` objects are not thread safe and
|
|
||||||
// cannot be used in parallel across goroutines.
|
|
||||||
// You can usually just use the Context.LoadFontFace function instead of
|
|
||||||
// this package-level function.
|
|
||||||
func LoadFontFaceFromFS(fsys fs.FS, path string, points float64) (font.Face, error) {
|
|
||||||
if fsys == nil {
|
|
||||||
switch {
|
|
||||||
case filepath.IsAbs(path):
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
orig = path
|
|
||||||
root = filepath.FromSlash("/")
|
|
||||||
)
|
|
||||||
path, err = filepath.Rel(root, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not find relative path for %q from %q: %w", orig, root, err)
|
|
||||||
}
|
|
||||||
fsys = os.DirFS(root)
|
|
||||||
default:
|
|
||||||
fsys = os.DirFS(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fontBytes, err := fs.ReadFile(fsys, path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return LoadFontFaceFromBytes(fontBytes, points)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadFontFace is a helper function to load the specified font with
|
|
||||||
// the specified point size. Note that the returned `font.Face` objects
|
|
||||||
// are not thread safe and cannot be used in parallel across goroutines.
|
|
||||||
// You can usually just use the Context.LoadFontFace function instead of
|
|
||||||
// this package-level function.
|
|
||||||
func LoadFontFaceFromBytes(raw []byte, points float64) (font.Face, error) {
|
|
||||||
f, err := opentype.Parse(raw)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
face, err := opentype.NewFace(f, &opentype.FaceOptions{
|
|
||||||
Size: points,
|
|
||||||
DPI: 72,
|
|
||||||
// Hinting: font.HintingFull,
|
|
||||||
})
|
|
||||||
return face, err
|
|
||||||
}
|
|
||||||
62
vendor/git.sr.ht/~sbinet/gg/wrap.go
vendored
62
vendor/git.sr.ht/~sbinet/gg/wrap.go
vendored
@@ -1,62 +0,0 @@
|
|||||||
// Copyright ©2022 The gg Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package gg
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
type measureStringer interface {
|
|
||||||
MeasureString(s string) (w, h float64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func splitOnSpace(x string) []string {
|
|
||||||
var result []string
|
|
||||||
pi := 0
|
|
||||||
ps := false
|
|
||||||
for i, c := range x {
|
|
||||||
s := unicode.IsSpace(c)
|
|
||||||
if s != ps && i > 0 {
|
|
||||||
result = append(result, x[pi:i])
|
|
||||||
pi = i
|
|
||||||
}
|
|
||||||
ps = s
|
|
||||||
}
|
|
||||||
result = append(result, x[pi:])
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func wordWrap(m measureStringer, s string, width float64) []string {
|
|
||||||
var result []string
|
|
||||||
for _, line := range strings.Split(s, "\n") {
|
|
||||||
fields := splitOnSpace(line)
|
|
||||||
if len(fields)%2 == 1 {
|
|
||||||
fields = append(fields, "")
|
|
||||||
}
|
|
||||||
x := ""
|
|
||||||
for i := 0; i < len(fields); i += 2 {
|
|
||||||
w, _ := m.MeasureString(x + fields[i])
|
|
||||||
if w > width {
|
|
||||||
if x == "" {
|
|
||||||
result = append(result, fields[i])
|
|
||||||
x = ""
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
result = append(result, x)
|
|
||||||
x = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
x += fields[i] + fields[i+1]
|
|
||||||
}
|
|
||||||
if x != "" {
|
|
||||||
result = append(result, x)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i, line := range result {
|
|
||||||
result[i] = strings.TrimSpace(line)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
47
vendor/github.com/airbusgeo/godal/CONTRIBUTING.md
generated
vendored
47
vendor/github.com/airbusgeo/godal/CONTRIBUTING.md
generated
vendored
@@ -1,47 +0,0 @@
|
|||||||
# How to contribute #
|
|
||||||
|
|
||||||
Thank you for stopping by. Please read these few small guidelines before
|
|
||||||
creating an issue or a pull request on godal.
|
|
||||||
|
|
||||||
|
|
||||||
## Reporting issues ##
|
|
||||||
|
|
||||||
Bugs, feature requests, and development-related questions should be directed to
|
|
||||||
our [GitHub issue tracker](https://github.com/airbusgeo/godal/issues). If
|
|
||||||
reporting a bug, please try and provide as much context as possible such as
|
|
||||||
your Go version, GDAL version and anything else that might be relevant to
|
|
||||||
the bug. For feature requests, please explain what you're trying to do, and
|
|
||||||
how the requested feature would help you do that.
|
|
||||||
|
|
||||||
## Submitting a patch ##
|
|
||||||
|
|
||||||
1. Patches are to be submitted through pull-requests: https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request
|
|
||||||
|
|
||||||
1. Make sure each group of changes be done in distinct branches in order to
|
|
||||||
ensure that a pull request only includes code related to that bug or feature.
|
|
||||||
|
|
||||||
1. Always run `go fmt` on your code before committing it.
|
|
||||||
|
|
||||||
1. Do not squash / force-push your commits inside the pull request branch as
|
|
||||||
these tend to mess up the review comments.
|
|
||||||
|
|
||||||
1. As far as possible, the public API exposed by godal should remain backwards
|
|
||||||
compatible, meaning that existing code using godal should compile correctly
|
|
||||||
when using a newer godal version, and that the resulting behavior of the
|
|
||||||
compiled program should be unchanged. godal makes heavy use of
|
|
||||||
[optional parameters](https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis)
|
|
||||||
in its exposed API in order to allow this.
|
|
||||||
|
|
||||||
1. Any changes should almost always be accompanied by tests. Look at some of
|
|
||||||
the existing tests if you're unsure how to go about it. Tests should ensure
|
|
||||||
that the godal wrapper itself is working correctly, not the underlying GDAL
|
|
||||||
library (e.g. the test could check that a particular option has been taken
|
|
||||||
into account by gdal, not that gdal has produced correct output for all
|
|
||||||
possible option values)
|
|
||||||
|
|
||||||
1. Pull requests will be automatically tested, vetted and checked for test
|
|
||||||
coverage. You can run the following tools locally before submitting your
|
|
||||||
changes to ensure the checks will pass:
|
|
||||||
* `go test ./... -cover`
|
|
||||||
* `golangci-lint run --skip-files doc_test.go`
|
|
||||||
|
|
||||||
191
vendor/github.com/airbusgeo/godal/LICENSE
generated
vendored
191
vendor/github.com/airbusgeo/godal/LICENSE
generated
vendored
@@ -1,191 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
95
vendor/github.com/airbusgeo/godal/README.md
generated
vendored
95
vendor/github.com/airbusgeo/godal/README.md
generated
vendored
@@ -1,95 +0,0 @@
|
|||||||
# Golang bindings for GDAL
|
|
||||||
[](https://pkg.go.dev/github.com/airbusgeo/godal)
|
|
||||||
[](https://github.com/airbusgeo/godal/blob/main/LICENSE)
|
|
||||||
[](https://github.com/airbusgeo/godal/actions?query=workflow%3Agodal+event%3Apush+branch%3Amain)
|
|
||||||
[](https://coveralls.io/github/airbusgeo/godal?branch=main)
|
|
||||||
[](https://goreportcard.com/report/github.com/airbusgeo/godal)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### Goals
|
|
||||||
|
|
||||||
Godal aims at providing an idiomatic go wrapper around the <img src="https://gdal.org/_static/gdalicon.png" width="25" height="25">
|
|
||||||
[GDAL](https://gdal.org) library:
|
|
||||||
|
|
||||||
* Function calls return a result and an error. The result will be valid if
|
|
||||||
no error was returned. The error message will contain the root cause of why
|
|
||||||
the error happened.
|
|
||||||
* Calls between go and native libraries incur some overhead. As such godal does
|
|
||||||
not strictly expose GDAL's API, but groups often-used calls in a single cgo function
|
|
||||||
to reduce this overhead. For example, C code like
|
|
||||||
```c++
|
|
||||||
hDS = GDALOpen(filename, GA_Readonly)
|
|
||||||
if (hDS == NULL) exit(1);
|
|
||||||
int sx = GDALGetRasterXSize(hDS);
|
|
||||||
int sy = GDALGetRasterYSize(hDS);
|
|
||||||
int nBands = GDALGetRasterCount(hDS);
|
|
||||||
printf("dataset size: %dx%dx%d\n",sx,sy,nBands);
|
|
||||||
for (int i=1; i<=nBands; i++) {
|
|
||||||
hBand = GDALGetRasterBand(hDS,i);
|
|
||||||
int ovrCount = GDALGetOverviewCount(hBand)
|
|
||||||
for(int o=0; o<=ovrCount; o++) {
|
|
||||||
GDALRasterBandH oBand = GDALGetOverview(hBand,o);
|
|
||||||
int osx = GDALGetRasterBandXSize(oBand);
|
|
||||||
int osy = GDALGetRasterBandYSize(oBand);
|
|
||||||
printf("overview %d size: %dx%d\n",o,osx,osy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
will be written as
|
|
||||||
```go
|
|
||||||
hDS,err := godal.Open(filename)
|
|
||||||
if err!=nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
structure := hDS.Structure()
|
|
||||||
fmt.Printf("dataset size: %dx%dx%d\n", structure.SizeX,structure.SizeY,structure.NBands)
|
|
||||||
for _,band := range hDS.Bands() {
|
|
||||||
for o,ovr := range band.Overviews() {
|
|
||||||
bstruct := ovr.Structure()
|
|
||||||
fmt.Printf("overview %d size: %dx%d\n",o,bstruct.SizeX,bstruct.SizeY)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
* Unfrequently used or non-default parameters are passed as options:
|
|
||||||
```go
|
|
||||||
ds,err := godal.Open(filename) //read-only
|
|
||||||
ds,err := godal.Open(filename, Update()) //read-write
|
|
||||||
```
|
|
||||||
* Godal exposes a VSI handler that can easily allow you to expose an
|
|
||||||
[io.ReaderAt](https://golang.org/pkg/io/#ReaderAt) as a filename that can be
|
|
||||||
opened by GDAL. A handler for opening `gs://` google cloud storage URIs is
|
|
||||||
provided through https://github.com/airbusgeo/osio
|
|
||||||
|
|
||||||
### Documentation
|
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/airbusgeo/godal)
|
|
||||||
contains the API reference and example code to get you started. The
|
|
||||||
`*_test.go` files can also be used as reference.
|
|
||||||
|
|
||||||
|
|
||||||
### Status
|
|
||||||
|
|
||||||
Godal is not feature complete. The raster side is nearing completion and
|
|
||||||
should remain stable. The vector and spatial-referencing sides are far from
|
|
||||||
complete, meaning that the API might evolve in backwards incompatible ways
|
|
||||||
until essential functionality is covered.
|
|
||||||
|
|
||||||
### Contributing
|
|
||||||
|
|
||||||
Contributions are welcome. Please read the [contribution guidelines](CONTRIBUTING.md)
|
|
||||||
before submitting fixes or enhancements.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
Godal requires a GDAL version greater than 3.0. Make sure the GDAL headers
|
|
||||||
are installed on the system used for compiling go+godal code. If using a GDAL
|
|
||||||
installation in a non standard location, you can set your `PKG_CONFIG_PATH`
|
|
||||||
environment variable, e.g. `export PKG_CONFIG_PATH=/opt/include/pkgconfig`.
|
|
||||||
|
|
||||||
### Licensing
|
|
||||||
Godal is licensed under the Apache License, Version 2.0. See
|
|
||||||
[LICENSE](https://github.com/airbusgeo/godal/blob/main/LICENSE) for the full
|
|
||||||
license text.
|
|
||||||
|
|
||||||
|
|
||||||
158
vendor/github.com/airbusgeo/godal/driver.go
generated
vendored
158
vendor/github.com/airbusgeo/godal/driver.go
generated
vendored
@@ -1,158 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
//DriverName is GDAL driver
|
|
||||||
type DriverName string
|
|
||||||
|
|
||||||
const (
|
|
||||||
//GTiff GeoTIFF
|
|
||||||
GTiff DriverName = "GTiff"
|
|
||||||
//GeoJSON RFCxxxx geojson
|
|
||||||
GeoJSON DriverName = "GeoJSON"
|
|
||||||
//Memory in memory driver
|
|
||||||
Memory DriverName = "Memory"
|
|
||||||
//VRT is a VRT
|
|
||||||
VRT DriverName = "VRT"
|
|
||||||
//Shapefile is an ESRI Shapefile
|
|
||||||
Shapefile DriverName = "ESRI Shapefile"
|
|
||||||
//GeoPackage is a geo-package
|
|
||||||
GeoPackage DriverName = "GPKG"
|
|
||||||
//JP2KAK is a Kakadu Jpeg2000
|
|
||||||
JP2KAK DriverName = "JP2KAK"
|
|
||||||
//OpenJPEG is an OpenJPEG JPEG2000
|
|
||||||
OpenJPEG DriverName = "OpenJPEG"
|
|
||||||
//DIMAP is a Dimap
|
|
||||||
DIMAP DriverName = "DIMAP"
|
|
||||||
//HFA is an erdas img
|
|
||||||
HFA DriverName = "HFA"
|
|
||||||
//Mitab is a mapinfo mif/tab file
|
|
||||||
Mitab DriverName = "Mitab"
|
|
||||||
//CSV comma-separated values driver
|
|
||||||
CSV DriverName = "CSV"
|
|
||||||
)
|
|
||||||
|
|
||||||
type driverMapping struct {
|
|
||||||
rasterName string
|
|
||||||
vectorName string
|
|
||||||
rasterRegister string
|
|
||||||
vectorRegister string
|
|
||||||
}
|
|
||||||
|
|
||||||
var driverMappings = map[DriverName]driverMapping{
|
|
||||||
GTiff: {
|
|
||||||
rasterName: "GTiff",
|
|
||||||
rasterRegister: "GDALRegister_GTiff",
|
|
||||||
},
|
|
||||||
Memory: {
|
|
||||||
rasterName: "MEM",
|
|
||||||
vectorName: "Memory",
|
|
||||||
rasterRegister: "GDALRegister_MEM",
|
|
||||||
vectorRegister: "RegisterOGRMEM",
|
|
||||||
},
|
|
||||||
GeoJSON: {
|
|
||||||
vectorName: "GeoJSON",
|
|
||||||
vectorRegister: "RegisterOGRGeoJSON",
|
|
||||||
},
|
|
||||||
VRT: {
|
|
||||||
rasterName: "VRT",
|
|
||||||
vectorName: "OGR_VRT",
|
|
||||||
rasterRegister: "GDALRegister_VRT",
|
|
||||||
vectorRegister: "RegisterOGRVRT",
|
|
||||||
},
|
|
||||||
Shapefile: {
|
|
||||||
vectorName: "ESRI Shapefile",
|
|
||||||
vectorRegister: "RegisterOGRShape",
|
|
||||||
},
|
|
||||||
GeoPackage: {
|
|
||||||
rasterName: "GPKG",
|
|
||||||
vectorName: "GPKG",
|
|
||||||
rasterRegister: "RegisterOGRGeoPackage",
|
|
||||||
vectorRegister: "RegisterOGRGeoPackage",
|
|
||||||
},
|
|
||||||
JP2KAK: {
|
|
||||||
rasterName: "JP2KAK",
|
|
||||||
rasterRegister: "GDALRegister_JP2KAK",
|
|
||||||
},
|
|
||||||
OpenJPEG: {
|
|
||||||
rasterName: "OpenJPEG",
|
|
||||||
rasterRegister: "GDALRegister_JP2OpenJPEG",
|
|
||||||
},
|
|
||||||
DIMAP: {
|
|
||||||
rasterName: "DIMAP",
|
|
||||||
rasterRegister: "GDALRegister_DIMAP",
|
|
||||||
},
|
|
||||||
HFA: {
|
|
||||||
rasterName: "HFA",
|
|
||||||
rasterRegister: "GDALRegister_HFA",
|
|
||||||
},
|
|
||||||
Mitab: {
|
|
||||||
vectorName: "Mapinfo File",
|
|
||||||
vectorRegister: "RegisterOGRTAB",
|
|
||||||
},
|
|
||||||
CSV: {
|
|
||||||
vectorName: "CSV",
|
|
||||||
vectorRegister: "RegisterOGRCSV",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dn DriverName) setDatasetVectorTranslateOpt(to *dsVectorTranslateOpts) {
|
|
||||||
to.driver = dn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dn DriverName) setDatasetTranslateOpt(to *dsTranslateOpts) {
|
|
||||||
to.driver = dn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dn DriverName) setDatasetWarpOpt(to *dsWarpOpts) {
|
|
||||||
to.driver = dn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dn DriverName) setRasterizeOpt(to *rasterizeOpts) {
|
|
||||||
to.driver = dn
|
|
||||||
}
|
|
||||||
|
|
||||||
type driversOpt struct {
|
|
||||||
drivers []string
|
|
||||||
}
|
|
||||||
|
|
||||||
//Drivers specifies the list of drivers that are allowed to try opening the dataset
|
|
||||||
func Drivers(drivers ...string) interface {
|
|
||||||
OpenOption
|
|
||||||
} {
|
|
||||||
return driversOpt{drivers}
|
|
||||||
}
|
|
||||||
func (do driversOpt) setOpenOpt(oo *openOpts) {
|
|
||||||
oo.drivers = append(oo.drivers, do.drivers...)
|
|
||||||
}
|
|
||||||
|
|
||||||
type driverOpenOption struct {
|
|
||||||
oo []string
|
|
||||||
}
|
|
||||||
|
|
||||||
//DriverOpenOption adds a list of Open Options (-oo switch) to the open command. Each keyval must
|
|
||||||
//be provided in a "KEY=value" format
|
|
||||||
func DriverOpenOption(keyval ...string) interface {
|
|
||||||
OpenOption
|
|
||||||
BuildVRTOption
|
|
||||||
} {
|
|
||||||
return driverOpenOption{keyval}
|
|
||||||
}
|
|
||||||
func (doo driverOpenOption) setOpenOpt(oo *openOpts) {
|
|
||||||
oo.options = append(oo.options, doo.oo...)
|
|
||||||
}
|
|
||||||
func (doo driverOpenOption) setBuildVRTOpt(bvo *buildVRTOpts) {
|
|
||||||
bvo.openOptions = append(bvo.openOptions, doo.oo...)
|
|
||||||
}
|
|
||||||
461
vendor/github.com/airbusgeo/godal/errors.go
generated
vendored
461
vendor/github.com/airbusgeo/godal/errors.go
generated
vendored
@@ -1,461 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errorHandlerMu sync.Mutex
|
|
||||||
var errorHandlerIndex int
|
|
||||||
|
|
||||||
// ErrorHandler is a function that can be used to override godal's default behavior
|
|
||||||
// of treating all messages with severity >= CE_Warning as errors. When an ErrorHandler
|
|
||||||
// is passed as an option to a godal function, all logs/errors emitted by gdal will be passed
|
|
||||||
// to this function, which can decide wether the parameters correspond to an actual error
|
|
||||||
// or not.
|
|
||||||
//
|
|
||||||
// If the ErrorHandler returns nil, the parent function will not return an error. It is up
|
|
||||||
// to the ErrorHandler to log the message if needed.
|
|
||||||
//
|
|
||||||
// If the ErrorHandler returns an error, that error will be returned as-is to the caller
|
|
||||||
// of the parent function
|
|
||||||
type ErrorHandler func(ec ErrorCategory, code int, msg string) error
|
|
||||||
|
|
||||||
type errorHandlerWrapper struct {
|
|
||||||
fn ErrorHandler
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorHandlers = make(map[int]*errorHandlerWrapper)
|
|
||||||
|
|
||||||
func registerErrorHandler(fn ErrorHandler) int {
|
|
||||||
errorHandlerMu.Lock()
|
|
||||||
defer errorHandlerMu.Unlock()
|
|
||||||
for errorHandlerIndex == 0 || errorHandlers[errorHandlerIndex] != nil {
|
|
||||||
errorHandlerIndex++
|
|
||||||
}
|
|
||||||
errorHandlers[errorHandlerIndex] = &errorHandlerWrapper{fn: fn}
|
|
||||||
return errorHandlerIndex
|
|
||||||
}
|
|
||||||
|
|
||||||
func getErrorHandler(i int) *errorHandlerWrapper {
|
|
||||||
errorHandlerMu.Lock()
|
|
||||||
defer errorHandlerMu.Unlock()
|
|
||||||
return errorHandlers[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func unregisterErrorHandler(i int) {
|
|
||||||
errorHandlerMu.Lock()
|
|
||||||
defer errorHandlerMu.Unlock()
|
|
||||||
delete(errorHandlers, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorAndLoggingOpts struct {
|
|
||||||
eh ErrorHandler
|
|
||||||
config []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorCallback struct {
|
|
||||||
fn ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
type errorAndLoggingOption interface {
|
|
||||||
setErrorAndLoggingOpt(elo *errorAndLoggingOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ErrLogger is an option to override default error handling.
|
|
||||||
//
|
|
||||||
// See ErrorHandler.
|
|
||||||
func ErrLogger(fn ErrorHandler) interface {
|
|
||||||
errorAndLoggingOption
|
|
||||||
AddGeometryOption
|
|
||||||
BandCreateMaskOption
|
|
||||||
BandIOOption
|
|
||||||
BoundsOption
|
|
||||||
BufferOption
|
|
||||||
BuildOverviewsOption
|
|
||||||
BuildVRTOption
|
|
||||||
ClearOverviewsOption
|
|
||||||
CloseOption
|
|
||||||
CopyLayerOption
|
|
||||||
CreateFeatureOption
|
|
||||||
CreateLayerOption
|
|
||||||
CreateSpatialRefOption
|
|
||||||
DatasetCreateMaskOption
|
|
||||||
DatasetCreateOption
|
|
||||||
DatasetIOOption
|
|
||||||
DatasetTranslateOption
|
|
||||||
DatasetVectorTranslateOption
|
|
||||||
DatasetWarpIntoOption
|
|
||||||
DatasetWarpOption
|
|
||||||
DeleteFeatureOption
|
|
||||||
DifferenceOption
|
|
||||||
FeatureCountOption
|
|
||||||
FillBandOption
|
|
||||||
FillNoDataOption
|
|
||||||
GeoJSONOption
|
|
||||||
GeometryTransformOption
|
|
||||||
GeometryReprojectOption
|
|
||||||
GeometryWKBOption
|
|
||||||
GeometryWKTOption
|
|
||||||
GetGeoTransformOption
|
|
||||||
GMLExportOption
|
|
||||||
HistogramOption
|
|
||||||
IntersectsOption
|
|
||||||
IntersectionOption
|
|
||||||
MetadataOption
|
|
||||||
NewFeatureOption
|
|
||||||
NewGeometryOption
|
|
||||||
OpenOption
|
|
||||||
PolygonizeOption
|
|
||||||
RasterizeGeometryOption
|
|
||||||
RasterizeOption
|
|
||||||
RasterizeIntoOption
|
|
||||||
SetColorInterpOption
|
|
||||||
SetColorTableOption
|
|
||||||
SetDescriptionOption
|
|
||||||
SetGeometryOption
|
|
||||||
SetFieldValueOption
|
|
||||||
SetNoDataOption
|
|
||||||
SetScaleOffsetOption
|
|
||||||
SetGeoTransformOption
|
|
||||||
SetGeometryColumnNameOption
|
|
||||||
SetProjectionOption
|
|
||||||
SetSpatialRefOption
|
|
||||||
SieveFilterOption
|
|
||||||
SimplifyOption
|
|
||||||
SpatialRefValidateOption
|
|
||||||
SubGeometryOption
|
|
||||||
TransformOption
|
|
||||||
UnionOption
|
|
||||||
UpdateFeatureOption
|
|
||||||
VSIHandlerOption
|
|
||||||
VSIOpenOption
|
|
||||||
VSIUnlinkOption
|
|
||||||
WKTExportOption
|
|
||||||
StatisticsOption
|
|
||||||
SetStatisticsOption
|
|
||||||
ClearStatisticsOption
|
|
||||||
GridOption
|
|
||||||
NearblackOption
|
|
||||||
DemOption
|
|
||||||
SetGCPsOption
|
|
||||||
GCPsToGeoTransformOption
|
|
||||||
RegisterPluginOption
|
|
||||||
} {
|
|
||||||
return errorCallback{fn}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec errorCallback) setErrorAndLoggingOpt(elo *errorAndLoggingOpts) {
|
|
||||||
elo.eh = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setAddGeometryOpt(ao *addGeometryOpts) {
|
|
||||||
ao.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBandCreateMaskOpt(o *bandCreateMaskOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBandIOOpt(o *bandIOOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBoundsOpt(o *boundsOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBufferOpt(o *bufferOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBuildOverviewsOpt(o *buildOvrOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setBuildVRTOpt(o *buildVRTOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setClearOverviewsOpt(o *clearOvrOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setCloseOpt(o *closeOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setCopyLayerOpt(o *copyLayerOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setCreateFeatureOpt(cfo *createFeatureOpts) {
|
|
||||||
cfo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setCreateLayerOpt(o *createLayerOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setCreateSpatialRefOpt(o *createSpatialRefOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetCreateMaskOpt(o *dsCreateMaskOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetCreateOpt(o *dsCreateOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetIOOpt(o *datasetIOOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetTranslateOpt(o *dsTranslateOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetVectorTranslateOpt(o *dsVectorTranslateOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetWarpIntoOpt(o *dsWarpIntoOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDatasetWarpOpt(o *dsWarpOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDeleteFeatureOpt(o *deleteFeatureOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDifferenceOpt(do *differenceOpts) {
|
|
||||||
do.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setFeatureCountOpt(o *featureCountOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setFillBandOpt(o *fillBandOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setFillnodataOpt(o *fillnodataOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeojsonOpt(o *geojsonOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeometryColumnNameOpt(o *setGeometryColumnNameOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeometryTransformOpt(o *geometryTransformOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeometryReprojectOpt(o *geometryReprojectOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeometryWKBOpt(o *geometryWKBOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGeometryWKTOpt(o *geometryWKTOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGetGeoTransformOpt(o *getGeoTransformOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGMLExportOpt(o *gmlExportOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setHistogramOpt(o *histogramOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setIntersectsOpt(o *intersectsOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setIntersectionOpt(o *intersectionOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setMetadataOpt(o *metadataOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDescriptionOpt(o *setDescriptionOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setNewFeatureOpt(o *newFeatureOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setNewGeometryOpt(o *newGeometryOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setOpenOpt(oo *openOpts) {
|
|
||||||
oo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setPolygonizeOpt(o *polygonizeOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setRasterizeGeometryOpt(o *rasterizeGeometryOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setRasterizeOpt(o *rasterizeOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setRasterizeIntoOpt(o *rasterizeIntoOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetColorInterpOpt(ndo *setColorInterpOpts) {
|
|
||||||
ndo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetColorTableOpt(ndo *setColorTableOpts) {
|
|
||||||
ndo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetGeometryOpt(o *setGeometryOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetFieldValueOpt(o *setFieldValueOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetGeoTransformOpt(o *setGeoTransformOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetProjectionOpt(o *setProjectionOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetNoDataOpt(ndo *setNodataOpts) {
|
|
||||||
ndo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetScaleOffsetOpt(soo *setScaleOffsetOpts) {
|
|
||||||
soo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetSpatialRefOpt(ndo *setSpatialRefOpts) {
|
|
||||||
ndo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSieveFilterOpt(sfo *sieveFilterOpts) {
|
|
||||||
sfo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSimplifyOpt(o *simplifyOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSpatialRefValidateOpt(o *spatialRefValidateOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSubGeometryOpt(so *subGeometryOpts) {
|
|
||||||
so.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setTransformOpt(o *trnOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setUnionOpt(uo *unionOpts) {
|
|
||||||
uo.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setUpdateFeatureOpt(o *updateFeatureOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setVSIHandlerOpt(o *vsiHandlerOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setVSIOpenOpt(o *vsiOpenOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setVSIUnlinkOpt(o *vsiUnlinkOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setWKTExportOpt(o *srWKTOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec errorCallback) setStatisticsOpt(o *statisticsOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec errorCallback) setSetStatisticsOpt(o *setStatisticsOpt) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ec errorCallback) setClearStatisticsOpt(o *clearStatisticsOpt) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGridCreateOpt(o *gridCreateOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGridOpt(o *gridOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setNearblackOpt(o *nearBlackOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setDemOpt(o *demOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setSetGCPsOpt(o *setGCPsOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setGCPsToGeoTransformOpts(o *gcpsToGeoTransformOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
func (ec errorCallback) setRegisterPluginOpt(o *registerPluginOpts) {
|
|
||||||
o.errorHandler = ec.fn
|
|
||||||
}
|
|
||||||
|
|
||||||
type multiError struct {
|
|
||||||
errs []error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error is the standard error interface
|
|
||||||
func (me *multiError) Error() string {
|
|
||||||
w := bytes.NewBufferString(me.errs[0].Error())
|
|
||||||
for i := 1; i < len(me.errs); i++ {
|
|
||||||
w.WriteByte('\n')
|
|
||||||
w.WriteString(me.errs[i].Error())
|
|
||||||
}
|
|
||||||
return w.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// As is the standard error wrapping interface
|
|
||||||
func (me *multiError) As(target interface{}) bool {
|
|
||||||
for _, err := range me.errs {
|
|
||||||
if errors.As(err, target) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is is the standard error wrapping interface
|
|
||||||
func (me *multiError) Is(target error) bool {
|
|
||||||
for _, err := range me.errs {
|
|
||||||
if errors.Is(err, target) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func combine(e1, e2 error) error {
|
|
||||||
switch {
|
|
||||||
case e1 == nil:
|
|
||||||
return e2
|
|
||||||
case e2 == nil:
|
|
||||||
return e1
|
|
||||||
}
|
|
||||||
if me1, ok := e1.(*multiError); ok {
|
|
||||||
if me2, ok := e2.(*multiError); ok {
|
|
||||||
me1.errs = append(me1.errs, me2.errs...)
|
|
||||||
} else {
|
|
||||||
me1.errs = append(me1.errs, e2)
|
|
||||||
}
|
|
||||||
return me1
|
|
||||||
} else if me2, ok := e2.(*multiError); ok {
|
|
||||||
me := &multiError{}
|
|
||||||
me.errs = make([]error, 1, len(me2.errs)+1)
|
|
||||||
me.errs[0] = e1
|
|
||||||
me.errs = append(me.errs, me2.errs...)
|
|
||||||
return me
|
|
||||||
} else {
|
|
||||||
return &multiError{errs: []error{e1, e2}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var SkipWarnings = ErrLogger(
|
|
||||||
func(ec ErrorCategory, code int, message string) error {
|
|
||||||
if ec > CE_Warning {
|
|
||||||
return errors.New(message)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
1916
vendor/github.com/airbusgeo/godal/godal.cpp
generated
vendored
1916
vendor/github.com/airbusgeo/godal/godal.cpp
generated
vendored
File diff suppressed because it is too large
Load Diff
4221
vendor/github.com/airbusgeo/godal/godal.go
generated
vendored
4221
vendor/github.com/airbusgeo/godal/godal.go
generated
vendored
File diff suppressed because it is too large
Load Diff
188
vendor/github.com/airbusgeo/godal/godal.h
generated
vendored
188
vendor/github.com/airbusgeo/godal/godal.h
generated
vendored
@@ -1,188 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
#ifndef _GODAL_H_
|
|
||||||
#define _GODAL_H_
|
|
||||||
|
|
||||||
#define _GNU_SOURCE 1
|
|
||||||
#include <gdal.h>
|
|
||||||
#include <gdal_alg.h>
|
|
||||||
#include <ogr_srs_api.h>
|
|
||||||
#include <cpl_conv.h>
|
|
||||||
#include "cpl_port.h"
|
|
||||||
#include <gdal_frmts.h>
|
|
||||||
|
|
||||||
#if GDAL_VERSION_NUM < 3000000
|
|
||||||
#error "this code is only compatible with gdal version >= 3.0"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
extern "C" {
|
|
||||||
#endif
|
|
||||||
typedef struct {
|
|
||||||
char *errMessage;
|
|
||||||
int handlerIdx;
|
|
||||||
int failed;
|
|
||||||
char **configOptions;
|
|
||||||
} cctx;
|
|
||||||
void godalSetMetadataItem(cctx *ctx, GDALMajorObjectH mo, char *ckey, char *cval, char *cdom);
|
|
||||||
void godalSetDescription(cctx *ctx, GDALMajorObjectH mo, char *desc);
|
|
||||||
void godalClearMetadata(cctx *ctx, GDALMajorObjectH mo, char *cdom);
|
|
||||||
GDALDatasetH godalOpen(cctx *ctx, const char *name, unsigned int nOpenFlags, const char *const *papszAllowedDrivers,
|
|
||||||
const char *const *papszOpenOptions, const char *const *papszSiblingFiles);
|
|
||||||
|
|
||||||
GDALDatasetH godalCreate(cctx *ctx, GDALDriverH drv, const char *name, int width, int height, int nbands,
|
|
||||||
GDALDataType dtype, char **creationOption);
|
|
||||||
|
|
||||||
void godalClose(cctx *ctx, GDALDatasetH ds);
|
|
||||||
int godalRegisterDriver(const char *funcname);
|
|
||||||
void godalRegisterPlugins();
|
|
||||||
void godalRegisterPlugin(cctx *ctx, const char *name);
|
|
||||||
void godalRasterSize(GDALDatasetH ds, int *xsize, int *ysize);
|
|
||||||
|
|
||||||
//returns a null terminated list of bands. the caller must free the returned list
|
|
||||||
GDALRasterBandH *godalRasterBands(GDALDatasetH ds);
|
|
||||||
OGRLayerH *godalVectorLayers(GDALDatasetH ds);
|
|
||||||
|
|
||||||
GDALRasterBandH* godalBandOverviews(GDALRasterBandH bnd);
|
|
||||||
|
|
||||||
void godalSetRasterNoDataValue(cctx *ctx, GDALRasterBandH bnd, double nd);
|
|
||||||
void godalSetDatasetNoDataValue(cctx *ctx, GDALDatasetH bnd, double nd);
|
|
||||||
void godalDeleteRasterNoDataValue(cctx *ctx, GDALRasterBandH bnd);
|
|
||||||
void godalSetRasterScaleOffset(cctx *ctx, GDALRasterBandH bnd, double scale, double offset);
|
|
||||||
void godalSetDatasetScaleOffset(cctx *ctx, GDALDatasetH bnd, double scale, double offset);
|
|
||||||
void godalSetRasterColorInterpretation(cctx *ctx, GDALRasterBandH bnd, GDALColorInterp ci);
|
|
||||||
GDALRasterBandH godalCreateMaskBand(cctx *ctx, GDALRasterBandH bnd, int flags);
|
|
||||||
GDALRasterBandH godalCreateDatasetMaskBand(cctx *ctx, GDALDatasetH ds, int flags);
|
|
||||||
OGRSpatialReferenceH godalCreateUserSpatialRef(cctx *ctx, char *userInput);
|
|
||||||
OGRSpatialReferenceH godalCreateWKTSpatialRef(cctx *ctx, char *wkt);
|
|
||||||
OGRSpatialReferenceH godalCreateProj4SpatialRef(cctx *ctx, char *proj);
|
|
||||||
OGRSpatialReferenceH godalCreateEPSGSpatialRef(cctx *ctx, int epsgCode);
|
|
||||||
void godalValidateSpatialRef(cctx *ctx, OGRSpatialReferenceH sr);
|
|
||||||
char* godalExportToWKT(cctx *ctx, OGRSpatialReferenceH sr);
|
|
||||||
OGRCoordinateTransformationH godalNewCoordinateTransformation(cctx *ctx, OGRSpatialReferenceH src, OGRSpatialReferenceH dst);
|
|
||||||
void godalDatasetSetSpatialRef(cctx *ctx, GDALDatasetH ds, OGRSpatialReferenceH sr);
|
|
||||||
void godalSetGeoTransform(cctx *ctx, GDALDatasetH ds, double *gt);
|
|
||||||
void godalGetGeoTransform(cctx *ctx, GDALDatasetH ds, double *gt);
|
|
||||||
void godalSetProjection(cctx *ctx, GDALDatasetH ds, char *wkt);
|
|
||||||
|
|
||||||
GDALDatasetH godalTranslate(cctx *ctx, char *dstName, GDALDatasetH ds, char **switches);
|
|
||||||
GDALDatasetH godalDatasetWarp(cctx *ctx, char *dstName, int nSrcCount, GDALDatasetH *srcDS, char **switches);
|
|
||||||
void godalDatasetWarpInto(cctx *ctx, GDALDatasetH dstDs, int nSrcCount, GDALDatasetH *srcDS, char **switches);
|
|
||||||
GDALDatasetH godalDatasetVectorTranslate(cctx *ctx, char *dstName, GDALDatasetH ds, char **switches);
|
|
||||||
GDALDatasetH godalRasterize(cctx *ctx, char *dstName, GDALDatasetH dstDS, GDALDatasetH ds, char **switches);
|
|
||||||
void godalRasterizeGeometry(cctx *ctx, GDALDatasetH ds, OGRGeometryH geom, int *bands, int nBands, double *vals, int allTouched);
|
|
||||||
void godalBuildOverviews(cctx *ctx, GDALDatasetH ds, const char *resampling, int nLevels, int *levels, int nBands, int *bands);
|
|
||||||
void godalClearOverviews(cctx *ctx, GDALDatasetH ds);
|
|
||||||
|
|
||||||
void godalDatasetStructure(GDALDatasetH ds, int *sx, int *sy, int *bsx, int *bsy, double *scale, double *offset, int *bandCount, int *dtype);
|
|
||||||
void godalBandStructure(GDALRasterBandH bnd, int *sx, int *sy, int *bsx, int *bsy, double *scale, double *offset, int *dtype);
|
|
||||||
void godalDatasetRasterIO(cctx *ctx, GDALDatasetH ds, GDALRWFlag rw, int nDSXOff, int nDSYOff, int nDSXSize, int nDSYSize, void *pBuffer,
|
|
||||||
int nBXSize, int nBYSize, GDALDataType eBDataType, int nBandCount, int *panBandCount,
|
|
||||||
int nPixelSpace, int nLineSpace, int nBandSpace, GDALRIOResampleAlg alg);
|
|
||||||
void godalBandRasterIO(cctx *ctx, GDALRasterBandH bnd, GDALRWFlag rw, int nDSXOff, int nDSYOff, int nDSXSize, int nDSYSize, void *pBuffer,
|
|
||||||
int nBXSize, int nBYSize, GDALDataType eBDataType, int nPixelSpace, int nLineSpace, GDALRIOResampleAlg alg);
|
|
||||||
void godalFillRaster(cctx *ctx, GDALRasterBandH bnd, double real, double imag);
|
|
||||||
void godalPolygonize(cctx *ctx, GDALRasterBandH in, GDALRasterBandH mask, OGRLayerH layer, int fieldIndex, char **opts);
|
|
||||||
void godalFillNoData(cctx *ctx, GDALRasterBandH in, GDALRasterBandH mask, int maxDistance, int iterations, char **opts);
|
|
||||||
void godalSieveFilter(cctx *ctx, GDALRasterBandH bnd, GDALRasterBandH mask, GDALRasterBandH dst, int sizeThreshold, int connectedNess);
|
|
||||||
|
|
||||||
void godalLayerGetExtent(cctx *ctx, OGRLayerH layer, OGREnvelope *envelope);
|
|
||||||
void godalLayerFeatureCount(cctx *ctx, OGRLayerH layer, int *count);
|
|
||||||
void godalLayerSetFeature(cctx *ctx, OGRLayerH layer, OGRFeatureH feat);
|
|
||||||
void godalLayerCreateFeature(cctx *ctx, OGRLayerH layer, OGRFeatureH feat);
|
|
||||||
OGRFeatureH godalLayerNewFeature(cctx *ctx, OGRLayerH layer, OGRGeometryH geom);
|
|
||||||
void godalLayerDeleteFeature(cctx *ctx, OGRLayerH layer, OGRFeatureH feat);
|
|
||||||
void godalLayerSetGeometryColumnName(cctx *ctx, OGRLayerH layer, char *name);
|
|
||||||
void godalFeatureSetGeometryColumnName(cctx *ctx, OGRFeatureH feat, char *name);
|
|
||||||
void godalFeatureSetGeometry(cctx *ctx, OGRFeatureH feat, OGRGeometryH geom);
|
|
||||||
void godalFeatureSetFieldInteger(cctx *ctx, OGRFeatureH feat, int fieldIndex, int value);
|
|
||||||
void godalFeatureSetFieldInteger64(cctx *ctx, OGRFeatureH feat, int fieldIndex, long long value);
|
|
||||||
void godalFeatureSetFieldDouble(cctx *ctx, OGRFeatureH feat, int fieldIndex, double value);
|
|
||||||
void godalFeatureSetFieldString(cctx *ctx, OGRFeatureH feat, int fieldIndex, char *value);
|
|
||||||
void godalFeatureSetFieldDateTime(cctx *ctx, OGRFeatureH feat, int fieldIndex, int year, int month, int day, int hour, int minute, int second, int tzFlag);
|
|
||||||
void godalFeatureSetFieldIntegerList(cctx *ctx, OGRFeatureH feat, int fieldIndex, int nbValues, int *values);
|
|
||||||
void godalFeatureSetFieldInteger64List(cctx *ctx, OGRFeatureH feat, int fieldIndex, int nbValues, long long *values);
|
|
||||||
void godalFeatureSetFieldDoubleList(cctx *ctx, OGRFeatureH feat, int fieldIndex, int nbValues, double *values);
|
|
||||||
void godalFeatureSetFieldStringList(cctx *ctx, OGRFeatureH feat, int fieldIndex, char **values);
|
|
||||||
void godalFeatureSetFieldBinary(cctx *ctx, OGRFeatureH feat, int fieldIndex, int nbBytes, void *value);
|
|
||||||
OGRLayerH godalCreateLayer(cctx *ctx, GDALDatasetH ds, char *name, OGRSpatialReferenceH sr, OGRwkbGeometryType gtype);
|
|
||||||
OGRLayerH godalCopyLayer(cctx *ctx, GDALDatasetH ds, OGRLayerH layer, char *name);
|
|
||||||
void VSIInstallGoHandler(cctx *ctx, const char *pszPrefix, size_t bufferSize, size_t cacheSize);
|
|
||||||
|
|
||||||
void godalGetColorTable(GDALRasterBandH bnd, GDALPaletteInterp *interp, int *nEntries, short **entries);
|
|
||||||
void godalSetColorTable(cctx *ctx, GDALRasterBandH bnd, GDALPaletteInterp interp, int nEntries, short *entries);
|
|
||||||
void godalRasterHistogram(cctx *ctx, GDALRasterBandH bnd, double *min, double *max, int *buckets,
|
|
||||||
unsigned long long **values, int bIncludeOutOfRange, int bApproxOK);
|
|
||||||
|
|
||||||
VSILFILE *godalVSIOpen(cctx *ctx, const char *name);
|
|
||||||
void godalVSIUnlink(cctx *ctx, const char *name);
|
|
||||||
char* godalVSIClose(VSILFILE *f);
|
|
||||||
size_t godalVSIRead(VSILFILE *f, void *buf, int len, char **errmsg);
|
|
||||||
void godal_OGR_G_AddGeometry(cctx *ctx, OGRGeometryH geom, OGRGeometryH subGeom);
|
|
||||||
OGRGeometryH godal_OGR_G_Simplify(cctx *ctx, OGRGeometryH in, double tolerance);
|
|
||||||
OGRGeometryH godal_OGR_G_Buffer(cctx *ctx, OGRGeometryH in, double tolerance, int segments);
|
|
||||||
OGRGeometryH godal_OGR_G_Difference(cctx *ctx, OGRGeometryH geom1, OGRGeometryH geom2);
|
|
||||||
OGRGeometryH godal_OGR_G_GetGeometryRef(cctx *ctx, OGRGeometryH in, int subGeomIndex);
|
|
||||||
int godal_OGR_G_Intersects(cctx *ctx, OGRGeometryH geom1, OGRGeometryH geom2);
|
|
||||||
OGRGeometryH godal_OGR_G_Intersection(cctx *ctx, OGRGeometryH geom1, OGRGeometryH geom2);
|
|
||||||
OGRGeometryH godal_OGR_G_Union(cctx *ctx, OGRGeometryH geom1, OGRGeometryH geom2);
|
|
||||||
OGRGeometryH godalNewGeometryFromGeoJSON(cctx *ctx, char *geoJSON);
|
|
||||||
OGRGeometryH godalNewGeometryFromWKT(cctx *ctx, char *wkt, OGRSpatialReferenceH sr);
|
|
||||||
OGRGeometryH godalNewGeometryFromWKB(cctx *ctx, void *wkb, int wkbLen,OGRSpatialReferenceH sr);
|
|
||||||
char* godalExportGeometryWKT(cctx *ctx, OGRGeometryH in);
|
|
||||||
char* godalExportGeometryGeoJSON(cctx *ctx, OGRGeometryH in, int precision);
|
|
||||||
char* godalExportGeometryGML(cctx *ctx, OGRGeometryH in, char **switches);
|
|
||||||
void godalExportGeometryWKB(cctx *ctx, void **wkb, int *wkbLen, OGRGeometryH in);
|
|
||||||
void godalGeometryTransformTo(cctx *ctx, OGRGeometryH geom, OGRSpatialReferenceH sr);
|
|
||||||
void godalGeometryTransform(cctx *ctx, OGRGeometryH geom, OGRCoordinateTransformationH trn, OGRSpatialReferenceH dst);
|
|
||||||
|
|
||||||
GDALDatasetH godalBuildVRT(cctx *ctx, char *dstname, char **sources, char **switches);
|
|
||||||
|
|
||||||
void test_godal_error_handling(cctx *ctx);
|
|
||||||
void godalClearRasterStatistics(cctx *ctx, GDALDatasetH ds);
|
|
||||||
void godalComputeRasterStatistics(cctx *ctx, GDALRasterBandH bnd, int bApproxOK, double *pdfMin, double *pdfMax, double *pdfMean, double *pdfStdDev);
|
|
||||||
int godalGetRasterStatistics(cctx *ctx, GDALRasterBandH bnd, int bApproxOK, double *pdfMin, double *pdfMax, double *pdfMean, double *pdfStdDev);
|
|
||||||
void godalSetRasterStatistics(cctx *ctx, GDALRasterBandH bnd, double dfMin, double dfMax, double dfMean, double dfStdDev);
|
|
||||||
void godalGridCreate(cctx *ctx, char *pszAlgorithm, GDALGridAlgorithm eAlgorithm, GUInt32 nPoints, const double *padfX, const double *padfY, const double *padfZ, double dfXMin, double dfXMax, double dfYMin, double dfYMax, GUInt32 nXSize, GUInt32 nYSize, GDALDataType eType, void *pData);
|
|
||||||
GDALDatasetH godalGrid(cctx *ctx, const char *pszDest, GDALDatasetH hSrcDS, char **switches);
|
|
||||||
GDALDatasetH godalNearblack(cctx *ctx, const char *pszDest, GDALDatasetH hDstDS, GDALDatasetH hSrcDS, char **switches);
|
|
||||||
GDALDatasetH godalDem(cctx *ctx, const char *pszDest, const char *pszProcessing, const char *pszColorFilename, GDALDatasetH hSrcDS, char **switches);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
const GDAL_GCP *gcpList;
|
|
||||||
int numGCPs;
|
|
||||||
} GCPsAndCount;
|
|
||||||
typedef struct {
|
|
||||||
char **pszIds;
|
|
||||||
char **pszInfos;
|
|
||||||
double *dfGCPPixels;
|
|
||||||
double *dfGCPLines;
|
|
||||||
double *dfGCPXs;
|
|
||||||
double *dfGCPYs;
|
|
||||||
double *dfGCPZs;
|
|
||||||
} goGCPList;
|
|
||||||
OGRSpatialReferenceH godalGetGCPSpatialRef(GDALDatasetH hSrcDS);
|
|
||||||
const GCPsAndCount godalGetGCPs(GDALDatasetH hSrcDS);
|
|
||||||
const char *godalGetGCPProjection(GDALDatasetH hSrcDS);
|
|
||||||
void godalSetGCPs(cctx *ctx, GDALDatasetH hSrcDS, int numGCPs, goGCPList GCPList, const char *pszGCPProjection);
|
|
||||||
void godalSetGCPs2(cctx *ctx, GDALDatasetH hSrcDS, int numGCPs, goGCPList GCPList, OGRSpatialReferenceH hSRS);
|
|
||||||
GDAL_GCP *goGCPListToGDALGCP(goGCPList GCPList, int numGCPs);
|
|
||||||
void godalGCPListToGeoTransform(cctx *ctx, goGCPList GCPList, int numGCPs, double *gt);
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
|
|
||||||
#endif // GO_GDAL_H_
|
|
||||||
110
vendor/github.com/airbusgeo/godal/histogram.go
generated
vendored
110
vendor/github.com/airbusgeo/godal/histogram.go
generated
vendored
@@ -1,110 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
// Histogram is a band's histogram.
|
|
||||||
type Histogram struct {
|
|
||||||
min, max float64
|
|
||||||
counts []uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bucket is a histogram entry. It spans [Min,Max] and contains Count entries.
|
|
||||||
type Bucket struct {
|
|
||||||
Min, Max float64
|
|
||||||
Count uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
//Len returns the number of buckets contained in the histogram
|
|
||||||
func (h Histogram) Len() int {
|
|
||||||
return len(h.counts)
|
|
||||||
}
|
|
||||||
|
|
||||||
//Bucket returns the i'th bucket in the histogram. i must be between 0 and Len()-1.
|
|
||||||
func (h Histogram) Bucket(i int) Bucket {
|
|
||||||
width := (h.max - h.min) / float64(len(h.counts))
|
|
||||||
return Bucket{
|
|
||||||
Min: h.min + width*float64(i),
|
|
||||||
Max: h.min + width*float64(i+1),
|
|
||||||
Count: h.counts[i],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type histogramOpts struct {
|
|
||||||
approx int
|
|
||||||
includeOutside int
|
|
||||||
min, max float64
|
|
||||||
buckets int32
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// HistogramOption is an option that can be passed to Band.Histogram()
|
|
||||||
//
|
|
||||||
// Available HistogramOptions are:
|
|
||||||
// - Approximate() to allow the algorithm to operate on a subset of the full resolution data
|
|
||||||
// - Intervals(count int, min,max float64) to compute a histogram with count buckets, spanning [min,max].
|
|
||||||
// Each bucket will be (max-min)/count wide. If not provided, the default histogram will be returned.
|
|
||||||
// - IncludeOutOfRange() to populate the first and last bucket with values under/over the specified min/max
|
|
||||||
// when used in conjuntion with Intervals()
|
|
||||||
// - ErrLogger
|
|
||||||
type HistogramOption interface {
|
|
||||||
setHistogramOpt(ho *histogramOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
type includeOutsideOpt struct{}
|
|
||||||
|
|
||||||
func (ioo includeOutsideOpt) setHistogramOpt(ho *histogramOpts) {
|
|
||||||
ho.includeOutside = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// IncludeOutOfRange populates the first and last bucket with values under/over the specified min/max
|
|
||||||
// when used in conjuntion with Intervals()
|
|
||||||
func IncludeOutOfRange() interface {
|
|
||||||
HistogramOption
|
|
||||||
} {
|
|
||||||
return includeOutsideOpt{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type approximateOkOption struct{}
|
|
||||||
|
|
||||||
func (aoo approximateOkOption) setHistogramOpt(ho *histogramOpts) {
|
|
||||||
ho.approx = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Approximate allows the histogram algorithm to operate on a subset of the full resolution data
|
|
||||||
func Approximate() interface {
|
|
||||||
HistogramOption
|
|
||||||
StatisticsOption
|
|
||||||
} {
|
|
||||||
return approximateOkOption{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type intervalsOption struct {
|
|
||||||
min, max float64
|
|
||||||
buckets int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (io intervalsOption) setHistogramOpt(ho *histogramOpts) {
|
|
||||||
ho.min = io.min
|
|
||||||
ho.max = io.max
|
|
||||||
ho.buckets = io.buckets
|
|
||||||
}
|
|
||||||
|
|
||||||
// Intervals computes a histogram with count buckets, spanning [min,max].
|
|
||||||
// Each bucket will be (max-min)/count wide. If not provided, the default histogram will be returned.
|
|
||||||
func Intervals(count int, min, max float64) interface {
|
|
||||||
HistogramOption
|
|
||||||
} {
|
|
||||||
return intervalsOption{min: min, max: max, buckets: int32(count)}
|
|
||||||
}
|
|
||||||
1530
vendor/github.com/airbusgeo/godal/options.go
generated
vendored
1530
vendor/github.com/airbusgeo/godal/options.go
generated
vendored
File diff suppressed because it is too large
Load Diff
106
vendor/github.com/airbusgeo/godal/srs.go
generated
vendored
106
vendor/github.com/airbusgeo/godal/srs.go
generated
vendored
@@ -1,106 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
type srWKTOpts struct {
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
//WKTExportOption is an option that can be passed to SpatialRef.WKT()
|
|
||||||
//
|
|
||||||
// Available WKTExportOptions are:
|
|
||||||
// - ErrLogger
|
|
||||||
type WKTExportOption interface {
|
|
||||||
setWKTExportOpt(sro *srWKTOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
type trnOpts struct {
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// TransformOption is an option that can be passed to NewTransform
|
|
||||||
//
|
|
||||||
// Available TransformOptions are:
|
|
||||||
// - ErrLogger
|
|
||||||
type TransformOption interface {
|
|
||||||
setTransformOpt(o *trnOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sr *SpatialRef) setBoundsOpt(o *boundsOpts) {
|
|
||||||
o.sr = sr
|
|
||||||
}
|
|
||||||
|
|
||||||
type boundsOpts struct {
|
|
||||||
sr *SpatialRef
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// BoundsOption is an option that can be passed to Dataset.Bounds or Geometry.Bounds
|
|
||||||
//
|
|
||||||
// Available options are:
|
|
||||||
// - *SpatialRef
|
|
||||||
// - ErrLogger
|
|
||||||
type BoundsOption interface {
|
|
||||||
setBoundsOpt(o *boundsOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
type createSpatialRefOpts struct {
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateSpatialRefOption is an option that can be passed when creating a new spatial
|
|
||||||
// reference object
|
|
||||||
//
|
|
||||||
// Available options are:
|
|
||||||
// - ErrLogger
|
|
||||||
type CreateSpatialRefOption interface {
|
|
||||||
setCreateSpatialRefOpt(so *createSpatialRefOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reprojectBounds(bnds [4]float64, src, dst *SpatialRef) ([4]float64, error) {
|
|
||||||
var ret [4]float64
|
|
||||||
trn, err := NewTransform(src, dst)
|
|
||||||
if err != nil {
|
|
||||||
return ret, fmt.Errorf("create coordinate transform: %w", err)
|
|
||||||
}
|
|
||||||
defer trn.Close()
|
|
||||||
x := []float64{bnds[0], bnds[0], bnds[2], bnds[2]}
|
|
||||||
y := []float64{bnds[1], bnds[3], bnds[3], bnds[1]}
|
|
||||||
err = trn.TransformEx(x, y, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
return ret, fmt.Errorf("reproject bounds: %w", err)
|
|
||||||
}
|
|
||||||
ret[0] = x[0]
|
|
||||||
ret[1] = y[0]
|
|
||||||
ret[2] = x[0]
|
|
||||||
ret[3] = y[0]
|
|
||||||
for i := 1; i < 4; i++ {
|
|
||||||
if x[i] < ret[0] {
|
|
||||||
ret[0] = x[i]
|
|
||||||
}
|
|
||||||
if x[i] > ret[2] {
|
|
||||||
ret[2] = x[i]
|
|
||||||
}
|
|
||||||
if y[i] < ret[1] {
|
|
||||||
ret[1] = y[i]
|
|
||||||
}
|
|
||||||
if y[i] > ret[3] {
|
|
||||||
ret[3] = y[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret, nil
|
|
||||||
}
|
|
||||||
61
vendor/github.com/airbusgeo/godal/statistics.go
generated
vendored
61
vendor/github.com/airbusgeo/godal/statistics.go
generated
vendored
@@ -1,61 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
// Statisitics on a given band.
|
|
||||||
type Statistics struct {
|
|
||||||
Min, Max, Mean, Std float64
|
|
||||||
Approximate bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type statisticsOpts struct {
|
|
||||||
approx int
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
//StatisticsOption is an option that can be passed to Band.Statistics
|
|
||||||
//
|
|
||||||
//Available Statistics options are:
|
|
||||||
// - Aproximate() to allow the satistics to be computed on overviews or a subset of all tiles.
|
|
||||||
// - ErrLogger
|
|
||||||
type StatisticsOption interface {
|
|
||||||
setStatisticsOpt(so *statisticsOpts)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (aoo approximateOkOption) setStatisticsOpt(so *statisticsOpts) {
|
|
||||||
so.approx = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
//SetStatistics is an option that can passed to Band.SetStatistics()
|
|
||||||
//Available options are:
|
|
||||||
// -ErrLogger
|
|
||||||
type SetStatisticsOption interface {
|
|
||||||
setSetStatisticsOpt(sts *setStatisticsOpt)
|
|
||||||
}
|
|
||||||
|
|
||||||
type setStatisticsOpt struct {
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
//ClearStatistics is an option passed to Dataset.ClearStatistics
|
|
||||||
//Available options are:
|
|
||||||
// -ErrLogger
|
|
||||||
type ClearStatisticsOption interface {
|
|
||||||
setClearStatisticsOpt(sts *clearStatisticsOpt)
|
|
||||||
}
|
|
||||||
|
|
||||||
type clearStatisticsOpt struct {
|
|
||||||
errorHandler ErrorHandler
|
|
||||||
}
|
|
||||||
114
vendor/github.com/airbusgeo/godal/structure.go
generated
vendored
114
vendor/github.com/airbusgeo/godal/structure.go
generated
vendored
@@ -1,114 +0,0 @@
|
|||||||
// Copyright 2021 Airbus Defence and Space
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package godal
|
|
||||||
|
|
||||||
// Block is a window inside a dataset, starting at pixel X0,Y0 and spanning
|
|
||||||
// W,H pixels.
|
|
||||||
type Block struct {
|
|
||||||
X0, Y0 int
|
|
||||||
W, H int
|
|
||||||
bw, bh int //block size
|
|
||||||
sx, sy int //img size
|
|
||||||
nx, ny int //num blocks
|
|
||||||
i, j int //cur
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the following block in scanline order. It returns Block{},false
|
|
||||||
// when there are no more blocks in the scanlines
|
|
||||||
func (b Block) Next() (Block, bool) {
|
|
||||||
nb := b
|
|
||||||
nb.i++
|
|
||||||
if nb.i >= nb.nx {
|
|
||||||
nb.i = 0
|
|
||||||
nb.j++
|
|
||||||
}
|
|
||||||
if nb.j >= nb.ny {
|
|
||||||
return Block{}, false
|
|
||||||
}
|
|
||||||
nb.X0 = nb.i * nb.bw
|
|
||||||
nb.Y0 = nb.j * nb.bh
|
|
||||||
nb.W, nb.H = actualBlockSize(nb.sx, nb.sy, nb.bw, nb.bh, nb.i, nb.j)
|
|
||||||
|
|
||||||
return nb, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockIterator returns the blocks covering a sizeX,sizeY dataset.
|
|
||||||
// All sizes must be strictly positive.
|
|
||||||
func BlockIterator(sizeX, sizeY int, blockSizeX, blockSizeY int) Block {
|
|
||||||
bl := Block{
|
|
||||||
X0: 0,
|
|
||||||
Y0: 0,
|
|
||||||
i: 0,
|
|
||||||
j: 0,
|
|
||||||
bw: blockSizeX,
|
|
||||||
bh: blockSizeY,
|
|
||||||
sx: sizeX,
|
|
||||||
sy: sizeY,
|
|
||||||
}
|
|
||||||
bl.nx, bl.ny = (sizeX+blockSizeX-1)/blockSizeX,
|
|
||||||
(sizeY+blockSizeY-1)/blockSizeY
|
|
||||||
bl.W, bl.H = actualBlockSize(sizeX, sizeY, blockSizeX, blockSizeY, 0, 0)
|
|
||||||
return bl
|
|
||||||
}
|
|
||||||
|
|
||||||
// BandStructure implements Structure for a Band
|
|
||||||
type BandStructure struct {
|
|
||||||
SizeX, SizeY int
|
|
||||||
BlockSizeX, BlockSizeY int
|
|
||||||
Scale, Offset float64
|
|
||||||
DataType DataType
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatasetStructure implements Structure for a Dataset
|
|
||||||
type DatasetStructure struct {
|
|
||||||
BandStructure
|
|
||||||
NBands int
|
|
||||||
}
|
|
||||||
|
|
||||||
// FirstBlock returns the topleft block definition
|
|
||||||
func (is BandStructure) FirstBlock() Block {
|
|
||||||
return BlockIterator(is.SizeX, is.SizeY, is.BlockSizeX, is.BlockSizeY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockCount returns the number of blocks in the x and y dimensions
|
|
||||||
func (is BandStructure) BlockCount() (int, int) {
|
|
||||||
return (is.SizeX + is.BlockSizeX - 1) / is.BlockSizeX,
|
|
||||||
(is.SizeY + is.BlockSizeY - 1) / is.BlockSizeY
|
|
||||||
}
|
|
||||||
|
|
||||||
// ActualBlockSize returns the number of pixels in the x and y dimensions
|
|
||||||
// that actually contain data for the given x,y block
|
|
||||||
func (is BandStructure) ActualBlockSize(blockX, blockY int) (int, int) {
|
|
||||||
return actualBlockSize(is.SizeX, is.SizeY, is.BlockSizeX, is.BlockSizeY, blockX, blockY)
|
|
||||||
}
|
|
||||||
|
|
||||||
func actualBlockSize(sizeX, sizeY int, blockSizeX, blockSizeY int, blockX, blockY int) (int, int) {
|
|
||||||
cx, cy := (sizeX+blockSizeX-1)/blockSizeX,
|
|
||||||
(sizeY+blockSizeY-1)/blockSizeY
|
|
||||||
if blockX < 0 || blockY < 0 || blockX >= cx || blockY >= cy {
|
|
||||||
return 0, 0
|
|
||||||
}
|
|
||||||
retx := blockSizeX
|
|
||||||
rety := blockSizeY
|
|
||||||
if blockX == cx-1 {
|
|
||||||
nXPixelOff := blockX * blockSizeX
|
|
||||||
retx = sizeX - nXPixelOff
|
|
||||||
}
|
|
||||||
if blockY == cy-1 {
|
|
||||||
nYPixelOff := blockY * blockSizeY
|
|
||||||
rety = sizeY - nYPixelOff
|
|
||||||
}
|
|
||||||
return retx, rety
|
|
||||||
}
|
|
||||||
220
vendor/github.com/ajstarks/svgo/LICENSE
generated
vendored
220
vendor/github.com/ajstarks/svgo/LICENSE
generated
vendored
@@ -1,220 +0,0 @@
|
|||||||
Creative Commons Attribution 4.0 International Public License
|
|
||||||
|
|
||||||
By exercising the Licensed Rights (defined below), You accept and agree to
|
|
||||||
be bound by the terms and conditions of this Creative Commons Attribution
|
|
||||||
4.0 International Public License ("Public License"). To the extent this
|
|
||||||
Public License may be interpreted as a contract, You are granted the
|
|
||||||
Licensed Rights in consideration of Your acceptance of these terms and
|
|
||||||
conditions, and the Licensor grants You such rights in consideration
|
|
||||||
of benefits the Licensor receives from making the Licensed Material
|
|
||||||
available under these terms and conditions.
|
|
||||||
|
|
||||||
Section 1 – Definitions.
|
|
||||||
|
|
||||||
Adapted Material means material subject to Copyright and Similar Rights
|
|
||||||
that is derived from or based upon the Licensed Material and in which
|
|
||||||
the Licensed Material is translated, altered, arranged, transformed, or
|
|
||||||
otherwise modified in a manner requiring permission under the Copyright
|
|
||||||
and Similar Rights held by the Licensor. For purposes of this Public
|
|
||||||
License, where the Licensed Material is a musical work, performance,
|
|
||||||
or sound recording, Adapted Material is always produced where the
|
|
||||||
Licensed Material is synched in timed relation with a moving image.
|
|
||||||
Adapter's License means the license You apply to Your Copyright and
|
|
||||||
Similar Rights in Your contributions to Adapted Material in accordance
|
|
||||||
with the terms and conditions of this Public License. Copyright and
|
|
||||||
Similar Rights means copyright and/or similar rights closely related to
|
|
||||||
copyright including, without limitation, performance, broadcast, sound
|
|
||||||
recording, and Sui Generis Database Rights, without regard to how the
|
|
||||||
rights are labeled or categorized. For purposes of this Public License,
|
|
||||||
the rights specified in Section 2(b)(1)-(2) are not Copyright and Similar
|
|
||||||
Rights. Effective Technological Measures means those measures that,
|
|
||||||
in the absence of proper authority, may not be circumvented under laws
|
|
||||||
fulfilling obligations under Article 11 of the WIPO Copyright Treaty
|
|
||||||
adopted on December 20, 1996, and/or similar international agreements.
|
|
||||||
Exceptions and Limitations means fair use, fair dealing, and/or any other
|
|
||||||
exception or limitation to Copyright and Similar Rights that applies to
|
|
||||||
Your use of the Licensed Material. Licensed Material means the artistic
|
|
||||||
or literary work, database, or other material to which the Licensor
|
|
||||||
applied this Public License. Licensed Rights means the rights granted
|
|
||||||
to You subject to the terms and conditions of this Public License, which
|
|
||||||
are limited to all Copyright and Similar Rights that apply to Your use
|
|
||||||
of the Licensed Material and that the Licensor has authority to license.
|
|
||||||
Licensor means the individual(s) or entity(ies) granting rights under
|
|
||||||
this Public License. Share means to provide material to the public by
|
|
||||||
any means or process that requires permission under the Licensed Rights,
|
|
||||||
such as reproduction, public display, public performance, distribution,
|
|
||||||
dissemination, communication, or importation, and to make material
|
|
||||||
available to the public including in ways that members of the public
|
|
||||||
may access the material from a place and at a time individually chosen
|
|
||||||
by them. Sui Generis Database Rights means rights other than copyright
|
|
||||||
resulting from Directive 96/9/EC of the European Parliament and of the
|
|
||||||
Council of 11 March 1996 on the legal protection of databases, as amended
|
|
||||||
and/or succeeded, as well as other essentially equivalent rights anywhere
|
|
||||||
in the world. You means the individual or entity exercising the Licensed
|
|
||||||
Rights under this Public License. Your has a corresponding meaning.
|
|
||||||
Section 2 – Scope.
|
|
||||||
|
|
||||||
License grant. Subject to the terms and conditions of this Public
|
|
||||||
License, the Licensor hereby grants You a worldwide, royalty-free,
|
|
||||||
non-sublicensable, non-exclusive, irrevocable license to exercise the
|
|
||||||
Licensed Rights in the Licensed Material to: reproduce and Share the
|
|
||||||
Licensed Material, in whole or in part; and produce, reproduce, and
|
|
||||||
Share Adapted Material. Exceptions and Limitations. For the avoidance
|
|
||||||
of doubt, where Exceptions and Limitations apply to Your use, this
|
|
||||||
Public License does not apply, and You do not need to comply with
|
|
||||||
its terms and conditions. Term. The term of this Public License is
|
|
||||||
specified in Section 6(a). Media and formats; technical modifications
|
|
||||||
allowed. The Licensor authorizes You to exercise the Licensed Rights in
|
|
||||||
all media and formats whether now known or hereafter created, and to make
|
|
||||||
technical modifications necessary to do so. The Licensor waives and/or
|
|
||||||
agrees not to assert any right or authority to forbid You from making
|
|
||||||
technical modifications necessary to exercise the Licensed Rights,
|
|
||||||
including technical modifications necessary to circumvent Effective
|
|
||||||
Technological Measures. For purposes of this Public License, simply making
|
|
||||||
modifications authorized by this Section 2(a)(4) never produces Adapted
|
|
||||||
Material. Downstream recipients. Offer from the Licensor – Licensed
|
|
||||||
Material. Every recipient of the Licensed Material automatically receives
|
|
||||||
an offer from the Licensor to exercise the Licensed Rights under the terms
|
|
||||||
and conditions of this Public License. No downstream restrictions. You
|
|
||||||
may not offer or impose any additional or different terms or conditions
|
|
||||||
on, or apply any Effective Technological Measures to, the Licensed
|
|
||||||
Material if doing so restricts exercise of the Licensed Rights by any
|
|
||||||
recipient of the Licensed Material. No endorsement. Nothing in this
|
|
||||||
Public License constitutes or may be construed as permission to assert
|
|
||||||
or imply that You are, or that Your use of the Licensed Material is,
|
|
||||||
connected with, or sponsored, endorsed, or granted official status by,
|
|
||||||
the Licensor or others designated to receive attribution as provided in
|
|
||||||
Section 3(a)(1)(A)(i). Other rights.
|
|
||||||
|
|
||||||
Moral rights, such as the right of integrity, are not licensed under
|
|
||||||
this Public License, nor are publicity, privacy, and/or other similar
|
|
||||||
personality rights; however, to the extent possible, the Licensor waives
|
|
||||||
and/or agrees not to assert any such rights held by the Licensor to the
|
|
||||||
limited extent necessary to allow You to exercise the Licensed Rights, but
|
|
||||||
not otherwise. Patent and trademark rights are not licensed under this
|
|
||||||
Public License. To the extent possible, the Licensor waives any right
|
|
||||||
to collect royalties from You for the exercise of the Licensed Rights,
|
|
||||||
whether directly or through a collecting society under any voluntary or
|
|
||||||
waivable statutory or compulsory licensing scheme. In all other cases
|
|
||||||
the Licensor expressly reserves any right to collect such royalties.
|
|
||||||
Section 3 – License Conditions.
|
|
||||||
|
|
||||||
Your exercise of the Licensed Rights is expressly made subject to the
|
|
||||||
following conditions.
|
|
||||||
|
|
||||||
Attribution.
|
|
||||||
|
|
||||||
If You Share the Licensed Material (including in modified form), You must:
|
|
||||||
|
|
||||||
retain the following if it is supplied by the Licensor with the Licensed
|
|
||||||
Material: identification of the creator(s) of the Licensed Material and
|
|
||||||
any others designated to receive attribution, in any reasonable manner
|
|
||||||
requested by the Licensor (including by pseudonym if designated); a
|
|
||||||
copyright notice; a notice that refers to this Public License; a notice
|
|
||||||
that refers to the disclaimer of warranties; a URI or hyperlink to the
|
|
||||||
Licensed Material to the extent reasonably practicable; indicate if You
|
|
||||||
modified the Licensed Material and retain an indication of any previous
|
|
||||||
modifications; and indicate the Licensed Material is licensed under this
|
|
||||||
Public License, and include the text of, or the URI or hyperlink to,
|
|
||||||
this Public License. You may satisfy the conditions in Section 3(a)(1)
|
|
||||||
in any reasonable manner based on the medium, means, and context in which
|
|
||||||
You Share the Licensed Material. For example, it may be reasonable to
|
|
||||||
satisfy the conditions by providing a URI or hyperlink to a resource
|
|
||||||
that includes the required information. If requested by the Licensor,
|
|
||||||
You must remove any of the information required by Section 3(a)(1)(A)
|
|
||||||
to the extent reasonably practicable. If You Share Adapted Material You
|
|
||||||
produce, the Adapter's License You apply must not prevent recipients of
|
|
||||||
the Adapted Material from complying with this Public License. Section 4
|
|
||||||
– Sui Generis Database Rights.
|
|
||||||
|
|
||||||
Where the Licensed Rights include Sui Generis Database Rights that apply
|
|
||||||
to Your use of the Licensed Material:
|
|
||||||
|
|
||||||
for the avoidance of doubt, Section 2(a)(1) grants You the right to
|
|
||||||
extract, reuse, reproduce, and Share all or a substantial portion of the
|
|
||||||
contents of the database; if You include all or a substantial portion of
|
|
||||||
the database contents in a database in which You have Sui Generis Database
|
|
||||||
Rights, then the database in which You have Sui Generis Database Rights
|
|
||||||
(but not its individual contents) is Adapted Material; and You must comply
|
|
||||||
with the conditions in Section 3(a) if You Share all or a substantial
|
|
||||||
portion of the contents of the database. For the avoidance of doubt,
|
|
||||||
this Section 4 supplements and does not replace Your obligations under
|
|
||||||
this Public License where the Licensed Rights include other Copyright and
|
|
||||||
Similar Rights. Section 5 – Disclaimer of Warranties and Limitation
|
|
||||||
of Liability.
|
|
||||||
|
|
||||||
Unless otherwise separately undertaken by the Licensor, to the
|
|
||||||
extent possible, the Licensor offers the Licensed Material as-is and
|
|
||||||
as-available, and makes no representations or warranties of any kind
|
|
||||||
concerning the Licensed Material, whether express, implied, statutory,
|
|
||||||
or other. This includes, without limitation, warranties of title,
|
|
||||||
merchantability, fitness for a particular purpose, non-infringement,
|
|
||||||
absence of latent or other defects, accuracy, or the presence or absence
|
|
||||||
of errors, whether or not known or discoverable. Where disclaimers of
|
|
||||||
warranties are not allowed in full or in part, this disclaimer may not
|
|
||||||
apply to You. To the extent possible, in no event will the Licensor
|
|
||||||
be liable to You on any legal theory (including, without limitation,
|
|
||||||
negligence) or otherwise for any direct, special, indirect, incidental,
|
|
||||||
consequential, punitive, exemplary, or other losses, costs, expenses,
|
|
||||||
or damages arising out of this Public License or use of the Licensed
|
|
||||||
Material, even if the Licensor has been advised of the possibility of
|
|
||||||
such losses, costs, expenses, or damages. Where a limitation of liability
|
|
||||||
is not allowed in full or in part, this limitation may not apply to You.
|
|
||||||
The disclaimer of warranties and limitation of liability provided above
|
|
||||||
shall be interpreted in a manner that, to the extent possible, most
|
|
||||||
closely approximates an absolute disclaimer and waiver of all liability.
|
|
||||||
Section 6 – Term and Termination.
|
|
||||||
|
|
||||||
This Public License applies for the term of the Copyright and Similar
|
|
||||||
Rights licensed here. However, if You fail to comply with this
|
|
||||||
Public License, then Your rights under this Public License terminate
|
|
||||||
automatically. Where Your right to use the Licensed Material has
|
|
||||||
terminated under Section 6(a), it reinstates:
|
|
||||||
|
|
||||||
automatically as of the date the violation is cured, provided it is
|
|
||||||
cured within 30 days of Your discovery of the violation; or upon express
|
|
||||||
reinstatement by the Licensor. For the avoidance of doubt, this Section
|
|
||||||
6(b) does not affect any right the Licensor may have to seek remedies
|
|
||||||
for Your violations of this Public License. For the avoidance of doubt,
|
|
||||||
the Licensor may also offer the Licensed Material under separate terms
|
|
||||||
or conditions or stop distributing the Licensed Material at any time;
|
|
||||||
however, doing so will not terminate this Public License. Sections 1,
|
|
||||||
5, 6, 7, and 8 survive termination of this Public License. Section 7
|
|
||||||
– Other Terms and Conditions.
|
|
||||||
|
|
||||||
The Licensor shall not be bound by any additional or different terms or
|
|
||||||
conditions communicated by You unless expressly agreed. Any arrangements,
|
|
||||||
understandings, or agreements regarding the Licensed Material not stated
|
|
||||||
herein are separate from and independent of the terms and conditions of
|
|
||||||
this Public License. Section 8 – Interpretation.
|
|
||||||
|
|
||||||
For the avoidance of doubt, this Public License does not, and shall not be
|
|
||||||
interpreted to, reduce, limit, restrict, or impose conditions on any use
|
|
||||||
of the Licensed Material that could lawfully be made without permission
|
|
||||||
under this Public License. To the extent possible, if any provision of
|
|
||||||
this Public License is deemed unenforceable, it shall be automatically
|
|
||||||
reformed to the minimum extent necessary to make it enforceable. If
|
|
||||||
the provision cannot be reformed, it shall be severed from this Public
|
|
||||||
License without affecting the enforceability of the remaining terms
|
|
||||||
and conditions. No term or condition of this Public License will be
|
|
||||||
waived and no failure to comply consented to unless expressly agreed
|
|
||||||
to by the Licensor. Nothing in this Public License constitutes or may
|
|
||||||
be interpreted as a limitation upon, or waiver of, any privileges and
|
|
||||||
immunities that apply to the Licensor or You, including from the legal
|
|
||||||
processes of any jurisdiction or authority. Creative Commons is not
|
|
||||||
a party to its public licenses. Notwithstanding, Creative Commons may
|
|
||||||
elect to apply one of its public licenses to material it publishes and
|
|
||||||
in those instances will be considered the “Licensor.” The text of
|
|
||||||
the Creative Commons public licenses is dedicated to the public domain
|
|
||||||
under the CC0 Public Domain Dedication. Except for the limited purpose of
|
|
||||||
indicating that material is shared under a Creative Commons public license
|
|
||||||
or as otherwise permitted by the Creative Commons policies published at
|
|
||||||
creativecommons.org/policies, Creative Commons does not authorize the
|
|
||||||
use of the trademark “Creative Commons” or any other trademark or
|
|
||||||
logo of Creative Commons without its prior written consent including,
|
|
||||||
without limitation, in connection with any unauthorized modifications
|
|
||||||
to any of its public licenses or any other arrangements, understandings,
|
|
||||||
or agreements concerning use of licensed material. For the avoidance of
|
|
||||||
doubt, this paragraph does not form part of the public licenses.
|
|
||||||
|
|
||||||
Creative Commons may be contacted at creativecommons.org.
|
|
||||||
3
vendor/github.com/ajstarks/svgo/LICENSE-link.txt
generated
vendored
3
vendor/github.com/ajstarks/svgo/LICENSE-link.txt
generated
vendored
@@ -1,3 +0,0 @@
|
|||||||
The contents of this repository are Licensed under
|
|
||||||
the Creative Commons Attribution 4.0 International license as described in
|
|
||||||
https://creativecommons.org/licenses/by/4.0/legalcode
|
|
||||||
739
vendor/github.com/ajstarks/svgo/README.markdown
generated
vendored
739
vendor/github.com/ajstarks/svgo/README.markdown
generated
vendored
@@ -1,739 +0,0 @@
|
|||||||
# SVGo: A Go library for SVG generation #
|
|
||||||
|
|
||||||
The library generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (<http://www.w3.org/TR/SVG11/>).
|
|
||||||
Output goes to the specified io.Writer.
|
|
||||||
|
|
||||||
## Supported SVG elements and functions ##
|
|
||||||
|
|
||||||
### Shapes, lines, text
|
|
||||||
|
|
||||||
circle, ellipse, polygon, polyline, rect (including roundrects), line, text
|
|
||||||
|
|
||||||
### Paths
|
|
||||||
|
|
||||||
general, arc, cubic and quadratic bezier paths,
|
|
||||||
|
|
||||||
### Image and Gradients
|
|
||||||
|
|
||||||
image, linearGradient, radialGradient,
|
|
||||||
|
|
||||||
### Transforms ###
|
|
||||||
|
|
||||||
translate, rotate, scale, skewX, skewY
|
|
||||||
|
|
||||||
### Animation ###
|
|
||||||
animate, animateMotion, animateTranslate, animateRotate, animateScale, animateSkewX, animateSkewY
|
|
||||||
|
|
||||||
### Filter Effects
|
|
||||||
|
|
||||||
filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting,
|
|
||||||
feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight,
|
|
||||||
feSpecularLighting, feSpotLight,feTile, feTurbulence
|
|
||||||
|
|
||||||
|
|
||||||
### Metadata elements ###
|
|
||||||
|
|
||||||
desc, defs, g (style, transform, id), marker, mask, pattern, title, (a)ddress, link, script, use
|
|
||||||
|
|
||||||
## Building and Usage ##
|
|
||||||
|
|
||||||
See svgdef.[svg|png|pdf] for a graphical view of the function calls
|
|
||||||
|
|
||||||
|
|
||||||
Usage: (assuming GOPATH is set)
|
|
||||||
|
|
||||||
go get github.com/ajstarks/svgo
|
|
||||||
go install github.com/ajstarks/svgo/...
|
|
||||||
|
|
||||||
|
|
||||||
You can use godoc to browse the documentation from the command line:
|
|
||||||
|
|
||||||
$ go doc github.com/ajstarks/svgo
|
|
||||||
|
|
||||||
|
|
||||||
a minimal program, to generate SVG to standard output.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ajstarks/svgo"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
width := 500
|
|
||||||
height := 500
|
|
||||||
canvas := svg.New(os.Stdout)
|
|
||||||
canvas.Start(width, height)
|
|
||||||
canvas.Circle(width/2, height/2, 100)
|
|
||||||
canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white")
|
|
||||||
canvas.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawing in a web server: (http://localhost:2003/circle)
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"github.com/ajstarks/svgo"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.Handle("/circle", http.HandlerFunc(circle))
|
|
||||||
err := http.ListenAndServe(":2003", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("ListenAndServe:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func circle(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "image/svg+xml")
|
|
||||||
s := svg.New(w)
|
|
||||||
s.Start(500, 500)
|
|
||||||
s.Circle(250, 250, 125, "fill:none;stroke:black")
|
|
||||||
s.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
You may view the SVG output with a browser that supports SVG (tested on Chrome, Opera, Firefox and Safari), or any other SVG user-agent such as Batik Squiggle.
|
|
||||||
|
|
||||||
### Graphics Sketching with SVGo and svgplay ###
|
|
||||||
|
|
||||||
Combined with the svgplay command, SVGo can be used to "sketch" with code in a browser.
|
|
||||||
|
|
||||||
To use svgplay and SVGo, first go to a directory with your code, and run:
|
|
||||||
|
|
||||||
$ svgplay
|
|
||||||
2014/06/25 22:05:28 ☠ ☠ ☠ Warning: this server allows a client connecting to 127.0.0.1:1999 to execute code on this computer ☠ ☠ ☠
|
|
||||||
|
|
||||||
Next open your browser to the svgplay server you just started.
|
|
||||||
svgplay only listens on localhost, and uses port 1999 (guess which year SVG was first introduced) by default
|
|
||||||
|
|
||||||
http://localhost:1999/
|
|
||||||
|
|
||||||
Enter your code in the textarea, and when you are ready to run press Shift--Enter. The code will be compiled, with the results
|
|
||||||
on the right. To update, change the code and repeat. Note that compilation errors are shown in red under the code. In order for svgplay/SVGo to work, make sure that the io.Writer specified with the New function is os.Stdout.
|
|
||||||
|
|
||||||
|
|
||||||
If you want to sketch with an existing file, enter its URL:
|
|
||||||
|
|
||||||
http://localhost:1999/foo.go
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
### SVGo Papers and presentations ###
|
|
||||||
|
|
||||||
* SVGo paper from SVGOpen 2011 <http://www.svgopen.org/2011/papers/34-SVGo_a_Go_Library_for_SVG_generation>
|
|
||||||
|
|
||||||
* Programming Pictures with SVGo <https://speakerdeck.com/u/ajstarks/p/programming-pictures-with-svgo>
|
|
||||||
|
|
||||||
* SVGo Workshop <https://speakerdeck.com/u/ajstarks/p/svgo-workshop>
|
|
||||||
|
|
||||||
|
|
||||||
### Tutorial Video ###
|
|
||||||
|
|
||||||
A video describing how to use the package can be seen on YouTube at <http://www.youtube.com/watch?v=ze6O2Dj5gQ4>
|
|
||||||
|
|
||||||
## Package contents ##
|
|
||||||
|
|
||||||
* svg.go: Library
|
|
||||||
* newsvg: Coding template command
|
|
||||||
* svgdef: Creates a SVG representation of the API
|
|
||||||
* animate: Animation demo
|
|
||||||
* am: Animate motion demo
|
|
||||||
* amt: Animate transformation demo
|
|
||||||
* android: The Android logo
|
|
||||||
* bubtrail: Bubble trails
|
|
||||||
* bulletgraph: Bullet Graphs (via Stephen Few)
|
|
||||||
* colortab: Display SVG named colors with RGB values
|
|
||||||
* compx: Component diagrams
|
|
||||||
* flower: Random "flowers"
|
|
||||||
* fontcompare: Compare two fonts
|
|
||||||
* f50: Get 50 photos from Flickr based on a query
|
|
||||||
* fe: Filter effects
|
|
||||||
* funnel: Funnel from transparent circles
|
|
||||||
* gradient: Linear and radial gradients
|
|
||||||
* html5logo: HTML5 logo with draggable elements
|
|
||||||
* imfade: Show image fading
|
|
||||||
* lewitt: Version of Sol Lewitt's Wall Drawing 91
|
|
||||||
* ltr: Layer Tennis Remixes
|
|
||||||
* marker: Test markers
|
|
||||||
* paths: Demonstrate SVG paths
|
|
||||||
* pattern: Test patterns
|
|
||||||
* planets: Show the scale of the Solar system
|
|
||||||
* pmap: Proportion maps
|
|
||||||
* randcomp: Compare random number generators
|
|
||||||
* richter: Gerhard Richter's 256 colors
|
|
||||||
* rl: Random lines (port of a Processing demo)
|
|
||||||
* skewabc: Skew ABC
|
|
||||||
* span: Text span
|
|
||||||
* stockproduct: Visualize product and stock prices
|
|
||||||
* svgopher: SVGo Mascot
|
|
||||||
* svgplay: SVGo sketching server
|
|
||||||
* svgplot: Plot data
|
|
||||||
* svgrid: Compose SVG files in a grid
|
|
||||||
* tsg: Twitter Search Grid
|
|
||||||
* tumblrgrid: Tumblr picture grid
|
|
||||||
* turbulence: Turbulence filter effect
|
|
||||||
* vismem: Visualize data from files
|
|
||||||
* webfonts: "Hello, World" with Google Web Fonts
|
|
||||||
* websvg: Generate SVG as a web server
|
|
||||||
|
|
||||||
|
|
||||||
## Functions and types ##
|
|
||||||
|
|
||||||
Many functions use x, y to specify an object's location, and w, h to specify the object's width and height.
|
|
||||||
Where applicable, a final optional argument specifies the style to be applied to the object.
|
|
||||||
The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a
|
|
||||||
series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: <http://www.w3.org/TR/SVG11/styling.html>)
|
|
||||||
|
|
||||||
The SVG type:
|
|
||||||
|
|
||||||
type SVG struct {
|
|
||||||
Writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
Most operations are methods on this type, specifying the destination io.Writer.
|
|
||||||
|
|
||||||
The Offcolor type:
|
|
||||||
|
|
||||||
type Offcolor struct {
|
|
||||||
Offset uint8
|
|
||||||
Color string
|
|
||||||
Opacity float64
|
|
||||||
}
|
|
||||||
|
|
||||||
is used to specify the offset, color, and opacity of stop colors in linear and radial gradients
|
|
||||||
|
|
||||||
The Filterspec type:
|
|
||||||
|
|
||||||
type Filterspec struct {
|
|
||||||
In string
|
|
||||||
In2 string
|
|
||||||
Result string
|
|
||||||
}
|
|
||||||
|
|
||||||
is used to specify inputs and results for filter effects
|
|
||||||
|
|
||||||
|
|
||||||
### Structure, Scripting, Metadata, Transformation and Links ###
|
|
||||||
|
|
||||||
New(w io.Writer) *SVG
|
|
||||||
Constructor, Specify the output destination.
|
|
||||||
|
|
||||||
Start(w int, h int, attributes ...string)
|
|
||||||
begin the SVG document with the width w and height h. Optionally add additional elements
|
|
||||||
(such as additional namespaces or scripting events)
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#SVGElement>
|
|
||||||
|
|
||||||
Startview(w, h, minx, miny, vw, vh int)
|
|
||||||
begin the SVG document with the width w, height h, with a viewBox at minx, miny, vw, vh.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#SVGElement>
|
|
||||||
|
|
||||||
Startunit(w int, h int, unit string, ns ...string)
|
|
||||||
begin the SVG document, with width and height in the specified units. Optionally add additional elements
|
|
||||||
(such as additional namespaces or scripting events)
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#SVGElement>
|
|
||||||
|
|
||||||
|
|
||||||
Startpercent(w int, h int, ns ...string)
|
|
||||||
begin the SVG document, with width and height in percent. Optionally add additional elements
|
|
||||||
(such as additional namespaces or scripting events)
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#SVGElement>
|
|
||||||
|
|
||||||
|
|
||||||
StartviewUnit(w, h int, unit string, minx, miny, vw, vh int)
|
|
||||||
begin the SVG document with the width w, height h, in the specified unit, with a viewBox at minx, miny, vw, vh.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#SVGElement>
|
|
||||||
|
|
||||||
End()
|
|
||||||
end the SVG document
|
|
||||||
|
|
||||||
Script(scriptype string, data ...string)
|
|
||||||
Script defines a script with a specified type, (for example "application/javascript").
|
|
||||||
if the first variadic argument is a link, use only the link reference.
|
|
||||||
Otherwise, treat variadic arguments as the text of the script (marked up as CDATA).
|
|
||||||
if no data is specified, simply close the script element.
|
|
||||||
<http://www.w3.org/TR/SVG/script.html>
|
|
||||||
|
|
||||||
Style(scriptype string, data ...string)
|
|
||||||
Style defines a script with a specified type, (for example "text/css").
|
|
||||||
if the first variadic argument is a link, use only the link reference.
|
|
||||||
Otherwise, treat variadic arguments as the text of the script (marked up as CDATA).
|
|
||||||
if no data is specified, simply close the style element.
|
|
||||||
<https://www.w3.org/TR/SVG/styling.html#StyleElement>
|
|
||||||
|
|
||||||
Group(s ...string)
|
|
||||||
begin a group, with arbitrary attributes
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#GElement>
|
|
||||||
|
|
||||||
Gstyle(s string)
|
|
||||||
begin a group, with the specified style.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#GElement>
|
|
||||||
|
|
||||||
Gid(s string)
|
|
||||||
begin a group, with the specified id.
|
|
||||||
|
|
||||||
Gtransform(s string)
|
|
||||||
begin a group, with the specified transform, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
Translate(x, y int)
|
|
||||||
begins coordinate translation to (x,y), end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
Scale(n float64)
|
|
||||||
scales the coordinate system by n, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
ScaleXY(x, y float64)
|
|
||||||
scales the coordinate system by x, y. End with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
SkewX(a float64)
|
|
||||||
SkewX skews the x coordinate system by angle a, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
SkewY(a float64)
|
|
||||||
SkewY skews the y coordinate system by angle a, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
SkewXY(ax, ay float64)
|
|
||||||
SkewXY skews x and y coordinate systems by ax, ay respectively, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
Rotate(r float64)
|
|
||||||
rotates the coordinate system by r degrees, end with Gend().
|
|
||||||
<http://www.w3.org/TR/SVG11/coords.html#TransformAttribute>
|
|
||||||
|
|
||||||
TranslateRotate(x, y int, r float64)
|
|
||||||
translates the coordinate system to (x,y), then rotates to r degrees, end with Gend().
|
|
||||||
|
|
||||||
RotateTranslate(x, y int, r float64)
|
|
||||||
rotates the coordinate system r degrees, then translates to (x,y), end with Gend().
|
|
||||||
|
|
||||||
Gend()
|
|
||||||
end the group (must be paired with Gstyle, Gtransform, Gid).
|
|
||||||
|
|
||||||
ClipPath(s ...string)
|
|
||||||
Begin a ClipPath
|
|
||||||
<http://www.w3.org/TR/SVG/masking.html#ClippingPaths>
|
|
||||||
|
|
||||||
ClipEnd()
|
|
||||||
End a ClipPath
|
|
||||||
<http://www.w3.org/TR/SVG/masking.html#ClippingPaths>
|
|
||||||
|
|
||||||
Def()
|
|
||||||
begin a definition block.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#DefsElement>
|
|
||||||
|
|
||||||
DefEnd()
|
|
||||||
end a definition block.
|
|
||||||
|
|
||||||
Marker(id string, x, y, w, h int, s ...string)
|
|
||||||
define a marker
|
|
||||||
<http://www.w3.org/TR/SVG11/painting.html#MarkerElement>
|
|
||||||
|
|
||||||
|
|
||||||
MarkerEnd()
|
|
||||||
end a marker
|
|
||||||
|
|
||||||
|
|
||||||
Mask(id string, x int, y int, w int, h int, s ...string)
|
|
||||||
creates a mask with a specified id, dimension, and optional style.
|
|
||||||
<http://www.w3.org/TR/SVG/masking.html>
|
|
||||||
|
|
||||||
MaskEnd()
|
|
||||||
ends the Mask element.
|
|
||||||
|
|
||||||
|
|
||||||
Pattern(id string, x, y, width, height int, putype string, s ...string)
|
|
||||||
define a Pattern with the specified dimensions, the putype can be either "user" or "obj", which sets the patternUnits
|
|
||||||
attribute to be either userSpaceOnUse or objectBoundingBox.
|
|
||||||
<http://www.w3.org/TR/SVG11/pservers.html#Patterns>
|
|
||||||
|
|
||||||
Desc(s string)
|
|
||||||
specify the text of the description.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#DescElement>
|
|
||||||
|
|
||||||
Title(s string)
|
|
||||||
specify the text of the title.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#TitleElement>
|
|
||||||
|
|
||||||
Link(href string, title string)
|
|
||||||
begin a link named "href", with the specified title.
|
|
||||||
<http://www.w3.org/TR/SVG11/linking.html#Links>
|
|
||||||
|
|
||||||
LinkEnd()
|
|
||||||
end the link.
|
|
||||||
|
|
||||||
Use(x int, y int, link string, s ...string)
|
|
||||||
place the object referenced at link at the location x, y.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#UseElement>
|
|
||||||
|
|
||||||
### Shapes ###
|
|
||||||
|
|
||||||
Circle(x int, y int, r int, s ...string)
|
|
||||||
draw a circle, centered at x,y with radius r.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#CircleElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Ellipse(x int, y int, w int, h int, s ...string)
|
|
||||||
draw an ellipse, centered at x,y with radii w, and h.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#EllipseElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Polygon(x []int, y []int, s ...string)
|
|
||||||
draw a series of line segments using an array of x, y coordinates.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#PolygonElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Rect(x int, y int, w int, h int, s ...string)
|
|
||||||
draw a rectangle with upper left-hand corner at x,y, with width w, and height h.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#RectElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
CenterRect(x int, y int, w int, h int, s ...string)
|
|
||||||
draw a rectangle with its center at x,y, with width w, and height h.
|
|
||||||
|
|
||||||
Roundrect(x int, y int, w int, h int, rx int, ry int, s ...string)
|
|
||||||
draw a rounded rectangle with upper the left-hand corner at x,y,
|
|
||||||
with width w, and height h. The radii for the rounded portion
|
|
||||||
is specified by rx (width), and ry (height).
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Square(x int, y int, s int, style ...string)
|
|
||||||
draw a square with upper left corner at x,y with sides of length s.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Paths ###
|
|
||||||
|
|
||||||
Path(p string, s ...style)
|
|
||||||
draw the arbitrary path as specified in p, according to the style specified in s. <http://www.w3.org/TR/SVG11/paths.html>
|
|
||||||
|
|
||||||
|
|
||||||
Arc(sx int, sy int, ax int, ay int, r int, large bool, sweep bool, ex int, ey int, s ...string)
|
|
||||||
draw an elliptical arc beginning coordinate at sx,sy, ending coordinate at ex, ey
|
|
||||||
width and height of the arc are specified by ax, ay, the x axis rotation is r
|
|
||||||
|
|
||||||
if sweep is true, then the arc will be drawn in a "positive-angle" direction (clockwise),
|
|
||||||
if false, the arc is drawn counterclockwise.
|
|
||||||
|
|
||||||
if large is true, the arc sweep angle is greater than or equal to 180 degrees,
|
|
||||||
otherwise the arc sweep is less than 180 degrees.
|
|
||||||
<http://www.w3.org/TR/SVG11/paths.html#PathDataEllipticalArcCommands>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Bezier(sx int, sy int, cx int, cy int, px int, py int, ex int, ey int, s ...string)
|
|
||||||
draw a cubic bezier curve, beginning at sx,sy, ending at ex,ey
|
|
||||||
with control points at cx,cy and px,py.
|
|
||||||
<http://www.w3.org/TR/SVG11/paths.html#PathDataCubicBezierCommands>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Qbezier(sx int, sy int, cx int, cy int, ex int, ey int, tx int, ty int, s ...string)
|
|
||||||
draw a quadratic bezier curve, beginning at sx, sy, ending at tx,ty
|
|
||||||
with control points are at cx,cy, ex,ey.
|
|
||||||
<http://www.w3.org/TR/SVG11/paths.html#PathDataQuadraticBezierCommands>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
Qbez(sx int, sy int, cx int, cy int, ex int, ey int, s...string)
|
|
||||||
draws a quadratic bezier curver, with optional style beginning at sx,sy, ending at ex, sy
|
|
||||||
with the control point at cx, cy.
|
|
||||||
<http://www.w3.org/TR/SVG11/paths.html#PathDataQuadraticBezierCommands>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Lines ###
|
|
||||||
|
|
||||||
Line(x1 int, y1 int, x2 int, y2 int, s ...string)
|
|
||||||
draw a line segment between x1,y1 and x2,y2.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#LineElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
|
|
||||||
Polyline(x []int, y []int, s ...string)
|
|
||||||
draw a polygon using coordinates specified in x,y arrays.
|
|
||||||
<http://www.w3.org/TR/SVG11/shapes.html#PolylineElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Image and Text ###
|
|
||||||
|
|
||||||
Image(x int, y int, w int, h int, link string, s ...string)
|
|
||||||
place at x,y (upper left hand corner), the image with width w, and height h, referenced at link.
|
|
||||||
<http://www.w3.org/TR/SVG11/struct.html#ImageElement>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
Text(x int, y int, t string, s ...string)
|
|
||||||
Place the specified text, t at x,y according to the optional style specified in s.
|
|
||||||
<http://www.w3.org/TR/SVG11/text.html#TextElement>
|
|
||||||
|
|
||||||
Textspan(x int, y int, t string, s ...string)
|
|
||||||
Place specified text, t at x,y according to the optional style specified in s.
|
|
||||||
<https://www.w3.org/TR/SVG11/text.html#TSpanElement>
|
|
||||||
Use this method with Span(...). End with TextEnd()
|
|
||||||
|
|
||||||
Span(t string, s ...string)
|
|
||||||
Create a text span t, using optional style s
|
|
||||||
|
|
||||||
TextEnd()
|
|
||||||
End a text span
|
|
||||||
|
|
||||||
Textlines(x, y int, s []string, size, spacing int, fill, align string)
|
|
||||||
Places lines of text in s, starting at x,y, at the specified size, fill, and alignment, and spacing.
|
|
||||||
|
|
||||||
Textpath(t string, pathid string, s ...string)
|
|
||||||
places optionally styled text along a previously defined path.
|
|
||||||
<http://www.w3.org/TR/SVG11/text.html#TextPathElement>
|
|
||||||

|
|
||||||
|
|
||||||
### Color ###
|
|
||||||
|
|
||||||
RGB(r int, g int, b int) string
|
|
||||||
creates a style string for the fill color designated
|
|
||||||
by the (r)ed, g(reen), (b)lue components.
|
|
||||||
<http://www.w3.org/TR/css3-color/>
|
|
||||||
|
|
||||||
RGBA(r int, g int, b int, a float64) string
|
|
||||||
as above, but includes the color's opacity as a value
|
|
||||||
between 0.0 (fully transparent) and 1.0 (opaque).
|
|
||||||
|
|
||||||
### Gradients ###
|
|
||||||
|
|
||||||
LinearGradient(id string, x1, y1, x2, y2 uint8, sc []Offcolor)
|
|
||||||
constructs a linear color gradient identified by id,
|
|
||||||
along the vector defined by (x1,y1), and (x2,y2).
|
|
||||||
The stop color sequence defined in sc. Coordinates are expressed as percentages.
|
|
||||||
<http://www.w3.org/TR/SVG11/pservers.html#LinearGradients>
|
|
||||||

|
|
||||||
|
|
||||||
RadialGradient(id string, cx, cy, r, fx, fy uint8, sc []Offcolor)
|
|
||||||
constructs a radial color gradient identified by id,
|
|
||||||
centered at (cx,cy), with a radius of r.
|
|
||||||
(fx, fy) define the location of the focal point of the light source.
|
|
||||||
The stop color sequence defined in sc.
|
|
||||||
Coordinates are expressed as percentages.
|
|
||||||
<http://www.w3.org/TR/SVG11/pservers.html#RadialGradients>
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Animation ###
|
|
||||||
|
|
||||||
Animate(link, attr string, from, to int, duration float64, repeat int, s ...string)
|
|
||||||
Animate animates the item referenced by the link, using the specified attribute
|
|
||||||
The animation starts at coordinate from, terminates at to, and repeats as specified.
|
|
||||||
Addtional attributes may be added as needed.
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateElement>
|
|
||||||
|
|
||||||
AnimateMotion(link, path string, duration float64, repeat int, s ...string)
|
|
||||||
AnimateMotion animates the referenced object ```link``` along the specified ```path```
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateMotionElement>
|
|
||||||
|
|
||||||
|
|
||||||
AnimateTranslate(link string, fx, fy, tx, ty int, duration float64, repeat int, s ...string)
|
|
||||||
AnimateTranslate animates the translation transformation (link refers to the object to animate, fx, fy are from coordinates, tx, ty are the to coordinates)
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateTransformElement>
|
|
||||||
|
|
||||||
AnimateRotate(link string, fs, fc, fe, ts, tc, te int, duration float64, repeat int, s ...string)
|
|
||||||
AnimateRotate animates the rotation transformation (link refers to the object to animate, f[s,c,e] are the from start, center, and end angles, t[s,c,e] are the
|
|
||||||
start, center, and end angles)
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateTransformElement>
|
|
||||||
|
|
||||||
|
|
||||||
AnimateScale(link string, from, to, duration float64, repeat int, s ...string)
|
|
||||||
AnimateScale animates the scale transformation (link refers to the object to animate, from and to specify the scaling factor)
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateTransformElement>
|
|
||||||
|
|
||||||
|
|
||||||
AnimateSkewX(link string, from, to, duration float64, repeat int, s ...string)
|
|
||||||
AnimateSkewX animates the skewX transformation ((link refers to the object to animate, from and to specify the skew angle)
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateTransformElement>
|
|
||||||
|
|
||||||
|
|
||||||
AnimateSkewY(link string, from, to, duration float64, repeat int, s ...string)
|
|
||||||
AnimateSkewY animates the skewY transformation (link refers to the object to animate, and from and to specify the skew angle)
|
|
||||||
<https://www.w3.org/TR/SVG11/animate.html#AnimateTransformElement>
|
|
||||||
|
|
||||||
|
|
||||||
### Filter Effects ###
|
|
||||||
|
|
||||||
Filter(id string, s ...string)
|
|
||||||
Filter begins a filter set
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#FilterElement>
|
|
||||||
|
|
||||||
Fend()
|
|
||||||
Fend ends a filter set
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#FilterElement>
|
|
||||||
|
|
||||||
FeBlend(fs Filterspec, mode string, s ...string)
|
|
||||||
FeBlend specifies a Blend filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feBlendElement>
|
|
||||||
|
|
||||||
FeColorMatrix(fs Filterspec, values [20]float64, s ...string)
|
|
||||||
FeColorMatrix specifies a color matrix filter primitive, with matrix values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement>
|
|
||||||
|
|
||||||
FeColorMatrixHue(fs Filterspec, value float64, s ...string)
|
|
||||||
FeColorMatrix specifies a color matrix filter primitive, with hue values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement>
|
|
||||||
|
|
||||||
FeColorMatrixSaturate(fs Filterspec, value float64, s ...string)
|
|
||||||
FeColorMatrix specifies a color matrix filter primitive, with saturation values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement>
|
|
||||||
|
|
||||||
FeColorMatrixLuminence(fs Filterspec, s ...string)
|
|
||||||
FeColorMatrix specifies a color matrix filter primitive, with luminence values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feColorMatrixElement>
|
|
||||||
|
|
||||||
FeComponentTransfer()
|
|
||||||
FeComponentTransfer begins a feComponent filter Element>
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement>
|
|
||||||
|
|
||||||
FeCompEnd()
|
|
||||||
FeCompEnd ends a feComponent filter Element>
|
|
||||||
|
|
||||||
FeComposite(fs Filterspec, operator string, k1, k2, k3, k4 int, s ...string)
|
|
||||||
FeComposite specifies a feComposite filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feCompositeElement>
|
|
||||||
|
|
||||||
FeConvolveMatrix(fs Filterspec, matrix [9]int, s ...string)
|
|
||||||
FeConvolveMatrix specifies a feConvolveMatrix filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feConvolveMatrixElement>
|
|
||||||
|
|
||||||
|
|
||||||
FeDiffuseLighting(fs Filterspec, scale, constant float64, s ...string)
|
|
||||||
FeDiffuseLighting specifies a diffuse lighting filter primitive,
|
|
||||||
a container for light source Element>s, end with DiffuseEnd()
|
|
||||||
|
|
||||||
FeDiffEnd()
|
|
||||||
FeDiffuseEnd ends a diffuse lighting filter primitive container
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feDiffuseLightingElement>
|
|
||||||
|
|
||||||
|
|
||||||
FeDisplacementMap(fs Filterspec, scale float64, xchannel, ychannel string, s ...string)
|
|
||||||
FeDisplacementMap specifies a feDisplacementMap filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement>
|
|
||||||
|
|
||||||
FeDistantLight(fs Filterspec, azimuth, elevation float64, s ...string)
|
|
||||||
FeDistantLight specifies a feDistantLight filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feDistantLightElement>
|
|
||||||
|
|
||||||
FeFlood(fs Filterspec, color string, opacity float64, s ...string)
|
|
||||||
FeFlood specifies a flood filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feFloodElement>
|
|
||||||
|
|
||||||
FeFuncLinear(channel string, slope, intercept float64)
|
|
||||||
FeFuncLinear is the linear form of feFunc
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement>
|
|
||||||
|
|
||||||
FeFuncGamma(channel, amplitude, exponent, offset float64)
|
|
||||||
FeFuncGamma is the gamma curve form of feFunc
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement>
|
|
||||||
|
|
||||||
FeFuncTable(channel string, tv []float64)
|
|
||||||
FeFuncGamma is the form of feFunc using a table of values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement>
|
|
||||||
|
|
||||||
FeFuncDiscrete(channel string, tv []float64)
|
|
||||||
FeFuncGamma is the form of feFunc using discrete values
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feComponentTransferElement>
|
|
||||||
|
|
||||||
FeGaussianBlur(fs Filterspec, stdx, stdy float64, s ...string)
|
|
||||||
FeGaussianBlur specifies a Gaussian Blur filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feGaussianBlurElement>
|
|
||||||
|
|
||||||
FeImage(href string, result string, s ...string)
|
|
||||||
FeImage specifies a feImage filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feImageElement>
|
|
||||||
|
|
||||||
FeMerge(nodes []string, s ...string)
|
|
||||||
FeMerge specifies a feMerge filter primitive, containing feMerge Element>s
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feMergeElement>
|
|
||||||
|
|
||||||
FeMorphology(fs Filterspec, operator string, xradius, yradius float64, s ...string)
|
|
||||||
FeMorphologyLight specifies a feMorphologyLight filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feMorphologyElement>
|
|
||||||
|
|
||||||
FeOffset(fs Filterspec, dx, dy int, s ...string)
|
|
||||||
FeOffset specifies the feOffset filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feOffsetElement>
|
|
||||||
|
|
||||||
FePointLight(x, y, z float64, s ...string)
|
|
||||||
FePointLight specifies a fePpointLight filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#fePointLightElement>
|
|
||||||
|
|
||||||
FeSpecularLighting(fs Filterspec, scale, constant float64, exponent int, color string, s ...string)
|
|
||||||
FeSpecularLighting specifies a specular lighting filter primitive,
|
|
||||||
a container for light source elements, end with SpecularEnd()
|
|
||||||
|
|
||||||
|
|
||||||
FeSpecEnd()
|
|
||||||
FeSpecularEnd ends a specular lighting filter primitive container
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feSpecularLightingElement>
|
|
||||||
|
|
||||||
|
|
||||||
FeSpotLight(fs Filterspec, x, y, z, px, py, pz float64, s ...string)
|
|
||||||
FeSpotLight specifies a feSpotLight filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feSpotLightElement>
|
|
||||||
|
|
||||||
FeTile(fs Filterspec, in string, s ...string)
|
|
||||||
FeTile specifies the tile utility filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feTileElement>
|
|
||||||
|
|
||||||
|
|
||||||
FeTurbulence(fs Filterspec, ftype string, bfx, bfy float64, octaves int, seed int64, stitch bool, s ...string)
|
|
||||||
FeTurbulence specifies a turbulence filter primitive
|
|
||||||
Standard reference: <http://www.w3.org/TR/SVG11/filters.html#feTurbulenceElement>
|
|
||||||
|
|
||||||
### Filter convenience functions (modeled on CSS filter effects) ###
|
|
||||||
|
|
||||||
Blur(p float64)
|
|
||||||
Blur function by standard deviation
|
|
||||||
|
|
||||||
Brightness(p float64)
|
|
||||||
Brightness function (0-100)
|
|
||||||
|
|
||||||
Grayscale()
|
|
||||||
Apply a grayscale filter to the image
|
|
||||||
|
|
||||||
HueRotate(a float64)
|
|
||||||
Rotate Hues (0-360 degrees)
|
|
||||||
|
|
||||||
Invert()
|
|
||||||
Invert the image's colors
|
|
||||||
|
|
||||||
Saturate(p float64)
|
|
||||||
Percent saturation, 0 is grayscale
|
|
||||||
|
|
||||||
Sepia()
|
|
||||||
Apply sepia tone
|
|
||||||
|
|
||||||
|
|
||||||
### Utility ###
|
|
||||||
|
|
||||||
Grid(x int, y int, w int, h int, n int, s ...string)
|
|
||||||
draws a grid of straight lines starting at x,y, with a width w, and height h, and a size of n.
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
### Credits ###
|
|
||||||
|
|
||||||
Thanks to Jonathan Wright for the io.Writer update.
|
|
||||||
126
vendor/github.com/ajstarks/svgo/doc.go
generated
vendored
126
vendor/github.com/ajstarks/svgo/doc.go
generated
vendored
@@ -1,126 +0,0 @@
|
|||||||
/*
|
|
||||||
Package svg generates SVG as defined by the Scalable Vector Graphics 1.1 Specification (<http://www.w3.org/TR/SVG11/>).
|
|
||||||
Output goes to the specified io.Writer.
|
|
||||||
|
|
||||||
Supported SVG elements and functions
|
|
||||||
|
|
||||||
Shapes, lines, text
|
|
||||||
|
|
||||||
circle, ellipse, polygon, polyline, rect (including roundrects), line, text
|
|
||||||
|
|
||||||
Paths
|
|
||||||
|
|
||||||
general, arc, cubic and quadratic bezier paths,
|
|
||||||
|
|
||||||
Image and Gradients
|
|
||||||
|
|
||||||
image, linearGradient, radialGradient,
|
|
||||||
|
|
||||||
Transforms
|
|
||||||
|
|
||||||
translate, rotate, scale, skewX, skewY
|
|
||||||
|
|
||||||
Filter Effects
|
|
||||||
|
|
||||||
filter, feBlend, feColorMatrix, feColorMatrix, feComponentTransfer, feComposite, feConvolveMatrix, feDiffuseLighting,
|
|
||||||
feDisplacementMap, feDistantLight, feFlood, feGaussianBlur, feImage, feMerge, feMorphology, feOffset, fePointLight,
|
|
||||||
feSpecularLighting, feSpotLight,feTile, feTurbulence
|
|
||||||
|
|
||||||
|
|
||||||
Metadata elements
|
|
||||||
|
|
||||||
desc, defs, g (style, transform, id), mask, marker, pattern, title, (a)ddress, link, script, style, use
|
|
||||||
|
|
||||||
Usage: (assuming GOPATH is set)
|
|
||||||
|
|
||||||
go get github.com/ajstarks/svgo
|
|
||||||
go install github.com/ajstarks/svgo/...
|
|
||||||
|
|
||||||
|
|
||||||
You can use godoc to browse the documentation from the command line:
|
|
||||||
|
|
||||||
$ godoc github.com/ajstarks/svgo
|
|
||||||
|
|
||||||
|
|
||||||
a minimal program, to generate SVG to standard output.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ajstarks/svgo"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
width := 500
|
|
||||||
height := 500
|
|
||||||
canvas := svg.New(os.Stdout)
|
|
||||||
canvas.Start(width, height)
|
|
||||||
canvas.Circle(width/2, height/2, 100)
|
|
||||||
canvas.Text(width/2, height/2, "Hello, SVG", "text-anchor:middle;font-size:30px;fill:white")
|
|
||||||
canvas.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
Drawing in a web server: (http://localhost:2003/circle)
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"github.com/ajstarks/svgo"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
http.Handle("/circle", http.HandlerFunc(circle))
|
|
||||||
err := http.ListenAndServe(":2003", nil)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("ListenAndServe:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func circle(w http.ResponseWriter, req *http.Request) {
|
|
||||||
w.Header().Set("Content-Type", "image/svg+xml")
|
|
||||||
s := svg.New(w)
|
|
||||||
s.Start(500, 500)
|
|
||||||
s.Circle(250, 250, 125, "fill:none;stroke:black")
|
|
||||||
s.End()
|
|
||||||
}
|
|
||||||
|
|
||||||
Functions and types
|
|
||||||
|
|
||||||
Many functions use x, y to specify an object's location, and w, h to specify the object's width and height.
|
|
||||||
Where applicable, a final optional argument specifies the style to be applied to the object.
|
|
||||||
The style strings follow the SVG standard; name:value pairs delimited by semicolons, or a
|
|
||||||
series of name="value" pairs. For example: `"fill:none; opacity:0.3"` or `fill="none" opacity="0.3"` (see: <http://www.w3.org/TR/SVG11/styling.html>)
|
|
||||||
|
|
||||||
The SVG type:
|
|
||||||
|
|
||||||
type SVG struct {
|
|
||||||
Writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
Most operations are methods on this type, specifying the destination io.Writer.
|
|
||||||
|
|
||||||
The Offcolor type:
|
|
||||||
|
|
||||||
type Offcolor struct {
|
|
||||||
Offset uint8
|
|
||||||
Color string
|
|
||||||
Opacity float64
|
|
||||||
}
|
|
||||||
|
|
||||||
is used to specify the offset, color, and opacity of stop colors in linear and radial gradients
|
|
||||||
|
|
||||||
The Filterspec type:
|
|
||||||
|
|
||||||
type Filterspec struct {
|
|
||||||
In string
|
|
||||||
In2 string
|
|
||||||
Result string
|
|
||||||
}
|
|
||||||
|
|
||||||
is used to specify inputs and results for filter effects
|
|
||||||
|
|
||||||
*/
|
|
||||||
package svg
|
|
||||||
39
vendor/github.com/ajstarks/svgo/newsvg
generated
vendored
39
vendor/github.com/ajstarks/svgo/newsvg
generated
vendored
@@ -1,39 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
if test $# -lt 1
|
|
||||||
then
|
|
||||||
echo "specify a file"
|
|
||||||
exit 2
|
|
||||||
fi
|
|
||||||
|
|
||||||
if test ! -f $1
|
|
||||||
then
|
|
||||||
cat <<! > $1
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/ajstarks/svgo"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
width = 500
|
|
||||||
height = 500
|
|
||||||
canvas = svg.New(os.Stdout)
|
|
||||||
)
|
|
||||||
|
|
||||||
func background(v int) { canvas.Rect(0, 0, width, height, canvas.RGB(v, v, v)) }
|
|
||||||
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
canvas.Start(width, height)
|
|
||||||
background(255)
|
|
||||||
|
|
||||||
// your code here
|
|
||||||
|
|
||||||
canvas.Grid(0, 0, width, height, 10, "stroke:black;opacity:0.1")
|
|
||||||
canvas.End()
|
|
||||||
}
|
|
||||||
!
|
|
||||||
fi
|
|
||||||
$EDITOR $1
|
|
||||||
1080
vendor/github.com/ajstarks/svgo/svg.go
generated
vendored
1080
vendor/github.com/ajstarks/svgo/svg.go
generated
vendored
File diff suppressed because it is too large
Load Diff
BIN
vendor/github.com/ajstarks/svgo/svgdef.pdf
generated
vendored
BIN
vendor/github.com/ajstarks/svgo/svgdef.pdf
generated
vendored
Binary file not shown.
395
vendor/github.com/ajstarks/svgo/svgdef.svg
generated
vendored
395
vendor/github.com/ajstarks/svgo/svgdef.svg
generated
vendored
@@ -1,395 +0,0 @@
|
|||||||
<?xml version="1.0"?>
|
|
||||||
<!-- Generated by SVGo -->
|
|
||||||
<svg width="4500" height="3375"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<desc>Object Definitions</desc>
|
|
||||||
<defs>
|
|
||||||
<linearGradient id="linear" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
||||||
<stop offset="0%" stop-color="white" stop-opacity="1.00"/>
|
|
||||||
<stop offset="25%" stop-color="lightblue" stop-opacity="1.00"/>
|
|
||||||
<stop offset="75%" stop-color="blue" stop-opacity="1.00"/>
|
|
||||||
<stop offset="100%" stop-color="rgb(0,0,127)" stop-opacity="1.00"/>
|
|
||||||
</linearGradient>
|
|
||||||
<radialGradient id="radial" cx="0%" cy="0%" r="100%" fx="50%" fy="50%">
|
|
||||||
<stop offset="0%" stop-color="white" stop-opacity="1.00"/>
|
|
||||||
<stop offset="25%" stop-color="lightblue" stop-opacity="1.00"/>
|
|
||||||
<stop offset="75%" stop-color="blue" stop-opacity="1.00"/>
|
|
||||||
<stop offset="100%" stop-color="rgb(0,0,127)" stop-opacity="1.00"/>
|
|
||||||
</radialGradient>
|
|
||||||
<path d="M 0,0 A62,62 0 0 1 250,0" id="tpath" />
|
|
||||||
<g id="square">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<rect x="0" y="0" width="125" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="62" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<text x="62" y="149" style="fill:black; text-anchor:middle;font-size:24px">Square(x, y, w int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="rect">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="-24" y="62" style="fill:gray; text-anchor:middle">h</text>
|
|
||||||
<text x="125" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Rect(x, y, w, h int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="crect">
|
|
||||||
<circle cx="125" cy="62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="38" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="-24" y="62" style="fill:gray; text-anchor:middle">h</text>
|
|
||||||
<text x="125" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">CenterRect(x, y, w, h int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="roundrect">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" rx="25" ry="25" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="-24" y="62" style="fill:gray; text-anchor:middle">h</text>
|
|
||||||
<text x="125" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<line x1="25" y1="0" x2="25" y2="25" style="stroke:black; stroke-width:1"/>
|
|
||||||
<line x1="0" y1="25" x2="25" y2="25" style="stroke:black; stroke-width:1"/>
|
|
||||||
<text x="49" y="13" style="fill:gray; text-anchor:middle">ry</text>
|
|
||||||
<text x="12" y="49" style="fill:gray; text-anchor:middle">rx</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Roundrect(x, y, w, h, rx, ry int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="polygon">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="125" cy="-31" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="-55" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="250" cy="93" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="69" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="125" cy="62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="38" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="0" cy="93" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="69" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<polygon points="0,0 125,-31 250,0 250,93 125,62 0,93" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Polygon(x, y []int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="circle">
|
|
||||||
<g transform="translate(125,62)">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="0" cy="0" r="62" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<line x1="0" y1="0" x2="62" y2="0" style="stroke:black; stroke-width:1"/>
|
|
||||||
<text x="31" y="24" style="fill:gray; text-anchor:middle">r</text>
|
|
||||||
<text x="0" y="86" style="fill:black; text-anchor:middle;font-size:24px">Circle(x, y, r int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="ellipse">
|
|
||||||
<g transform="translate(125,62)">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<ellipse cx="0" cy="0" rx="125" ry="62" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<line x1="0" y1="0" x2="125" y2="0" style="stroke:black; stroke-width:1"/>
|
|
||||||
<line x1="0" y1="0" x2="0" y2="62" style="stroke:black; stroke-width:1"/>
|
|
||||||
<text x="62" y="24" style="fill:gray; text-anchor:middle">rx</text>
|
|
||||||
<text x="-24" y="31" style="fill:gray; text-anchor:middle">ry</text>
|
|
||||||
<text x="0" y="86" style="fill:black; text-anchor:middle;font-size:24px">Ellipse(x, y, rx, ry int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="line">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x1, y1</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">x2, y2</text>
|
|
||||||
<line x1="0" y1="0" x2="250" y2="0" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Line(x1, y1, x2, y2 int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="polyline">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="83" cy="-62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="83" y="-86" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="187" cy="-41" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="187" y="-65" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<circle cx="250" cy="-125" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-149" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<polyline points="0,0 83,-62 187,-41 250,-125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Polyline(x, y []int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="arc">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">sx, sy</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">ex, ey</text>
|
|
||||||
<path d="M0,0 A62,62 0 0 1 250,0" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="148" style="fill:black; text-anchor:middle;font-size:24px">Arc(sx, sy, ax, ay, r int, lflag, sflag bool, ex, ey int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="path">
|
|
||||||
<path d="M36,5l12,41l12-41h33v4l-13,21c30,10,2,69-21,28l7-2c15,27,33,-22,3,-19v-4l12-20h-15l-17,59h-1l-13-42l-12,42h-1l-20-67h9l12,41l8-28l-4-13h9" fill="rgb(0,0,127)" />
|
|
||||||
<path d="M94,53c15,32,30,14,35,7l-1-7c-16,26-32,3-34,0M122,16c-10-21-34,0-21,30c-5-30 16,-38 23,-21l5-10l-2-9" style="fill-opacity:0.50; fill:rgb(0,0,0)"/>
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<text x="62" y="160" style="fill:black; text-anchor:middle;font-size:24px">Path(s string, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="qbez">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">sx, sy</text>
|
|
||||||
<circle cx="41" cy="-93" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="41" y="-117" style="fill:gray; text-anchor:middle">cx, cy</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">ex, ey</text>
|
|
||||||
<path d="M0,0 Q41,-93 250,0" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Qbez(sx, sy, cx, cy, ex, ey int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="bezier">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">sx, sy</text>
|
|
||||||
<circle cx="125" cy="-62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="-86" style="fill:gray; text-anchor:middle">cx, cy</text>
|
|
||||||
<circle cx="125" cy="62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="38" style="fill:gray; text-anchor:middle">px, py</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">ex, ey</text>
|
|
||||||
<path d="M0,0 C125,-62 125,62 250,0" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Bezier(sx, sy, cx, cy, px, py, ex, ey int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="image">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<rect x="0" y="0" width="128" height="128" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<text x="-24" y="64" style="fill:gray; text-anchor:middle">h</text>
|
|
||||||
<text x="64" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<image x="0" y="0" width="128" height="128" xlink:href="gophercolor128x128.png" />
|
|
||||||
<text x="64" y="152" style="fill:black; text-anchor:middle;font-size:24px">Image(x, y, w, h, int path string, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="lgrad">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:url(#linear)"/>
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x1%, y1%</text>
|
|
||||||
<circle cx="250" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="250" y="-24" style="fill:gray; text-anchor:middle">x2%, y2%</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">LinearGradient(s string, x1, y1, x2, y2 uint8, oc []Offcolor)</text>
|
|
||||||
</g>
|
|
||||||
<g id="rgrad">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:url(#radial)"/>
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">cx%, cy%</text>
|
|
||||||
<circle cx="125" cy="62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="125" y="38" style="fill:gray; text-anchor:middle">fx%, fy%</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">RadialGradient(s string, cx, cy, r, fx, fy uint8, oc []Offcolor)</text>
|
|
||||||
</g>
|
|
||||||
<g id="trans">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<circle cx="167" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="167" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Translate(x, y int)</text>
|
|
||||||
<rect x="0" y="0" width="83" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="translate(167,0)">
|
|
||||||
<rect x="0" y="0" width="83" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="grid">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<text x="-24" y="62" style="fill:gray; text-anchor:middle">h</text>
|
|
||||||
<text x="125" y="-24" style="fill:gray; text-anchor:middle">w</text>
|
|
||||||
<text x="55" y="15" style="fill:gray; text-anchor:middle">n</text>
|
|
||||||
<g style="stroke:rgb(0,0,127)">
|
|
||||||
<line x1="0" y1="0" x2="0" y2="125" />
|
|
||||||
<line x1="31" y1="0" x2="31" y2="125" />
|
|
||||||
<line x1="62" y1="0" x2="62" y2="125" />
|
|
||||||
<line x1="93" y1="0" x2="93" y2="125" />
|
|
||||||
<line x1="124" y1="0" x2="124" y2="125" />
|
|
||||||
<line x1="155" y1="0" x2="155" y2="125" />
|
|
||||||
<line x1="186" y1="0" x2="186" y2="125" />
|
|
||||||
<line x1="217" y1="0" x2="217" y2="125" />
|
|
||||||
<line x1="248" y1="0" x2="248" y2="125" />
|
|
||||||
<line x1="0" y1="0" x2="250" y2="0" />
|
|
||||||
<line x1="0" y1="31" x2="250" y2="31" />
|
|
||||||
<line x1="0" y1="62" x2="250" y2="62" />
|
|
||||||
<line x1="0" y1="93" x2="250" y2="93" />
|
|
||||||
<line x1="0" y1="124" x2="250" y2="124" />
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Grid(x, y, w, h, n int, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="text">
|
|
||||||
<circle cx="0" cy="62" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="86" style="fill:gray; text-anchor:middle">x, y</text>
|
|
||||||
<text x="0" y="62" style="text-anchor:start;font-size:32pt">hello, this is SVG</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Text(x, y int, s string, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="scale">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="scale(0.5)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Scale(n float64)</text>
|
|
||||||
</g>
|
|
||||||
<g id="scalexy">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="scale(0.5,0.75)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">ScaleXY(x, y float64)</text>
|
|
||||||
</g>
|
|
||||||
<g id="skewx">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="skewX(30)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">SkewX(a float64)</text>
|
|
||||||
</g>
|
|
||||||
<g id="skewy">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="skewY(10)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">SkewY(a float64)</text>
|
|
||||||
</g>
|
|
||||||
<g id="skewxy">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<g transform="skewX(10) skewY(10)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">SkewXY(x, y float64)</text>
|
|
||||||
</g>
|
|
||||||
<g id="rotate">
|
|
||||||
<circle cx="0" cy="0" r="4" style="fill:rgb(220,220,220)"/>
|
|
||||||
<text x="0" y="-24" style="fill:gray; text-anchor:middle">0, 0</text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Rotate(r float64)</text>
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill:none; stroke-width:2;stroke:rgb(0,0,127)"/>
|
|
||||||
<path d="M125,0 Q135,31 108,62" style="fill:none;stroke:gray"/>
|
|
||||||
<text x="62" y="24" style="fill:gray; text-anchor:middle">r</text>
|
|
||||||
<g transform="rotate(30)">
|
|
||||||
<rect x="0" y="0" width="250" height="125" style="fill-opacity:0.25;fill:rgb(0,0,127)"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="textpath">
|
|
||||||
<text fill="rgb(0,0,127)" text-anchor="start" font-size="16pt" ><textPath xlink:href="#tpath">It's "fine" & "dandy" to draw text along a path</textPath></text>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">Textpath(s, pathid string, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="meta">
|
|
||||||
<g style="font-size:24px;fill:black;text-anchor:start">
|
|
||||||
<text x="0" y="24" >New(w io Writer)</text>
|
|
||||||
<text x="0" y="52" >Start(w, h int, options ...string)/End()</text>
|
|
||||||
<text x="0" y="80" >Startview(w, h, minx, miny, vw, vh int)</text>
|
|
||||||
<text x="0" y="108" >Group(s ...string)/Gend()</text>
|
|
||||||
<text x="0" y="136" >Gstyle(s string)/Gend()</text>
|
|
||||||
<text x="0" y="164" >Gtransform(s string)/Gend()</text>
|
|
||||||
<text x="0" y="192" >Gid(id string)/Gend()</text>
|
|
||||||
<text x="0" y="220" >ClipPath(s ...string)/ClipEnd()</text>
|
|
||||||
<text x="0" y="248" >Def()/DefEnd()</text>
|
|
||||||
<text x="0" y="276" >Marker()/MarkerEnd()</text>
|
|
||||||
<text x="0" y="304" >Pattern()/PatternEnd()</text>
|
|
||||||
<text x="0" y="332" >Desc(s string)</text>
|
|
||||||
<text x="0" y="360" >Title(s string)</text>
|
|
||||||
<text x="0" y="388" >Script(type, data ...string)</text>
|
|
||||||
<text x="0" y="416" >Mask(id string, x,y,w,h int, style ...string)/MaskEnd()</text>
|
|
||||||
<text x="0" y="444" >Link(href string, title string)/LinkEnd()</text>
|
|
||||||
<text x="0" y="472" >Use(x int, y int, link string, style ...string)</text>
|
|
||||||
</g>
|
|
||||||
<g style="font-size:24px;fill:rgb(127,127,127);text-anchor:start">
|
|
||||||
<text x="650" y="24" >specify destination</text>
|
|
||||||
<text x="650" y="52" >begin/end the document</text>
|
|
||||||
<text x="650" y="80" >begin/end the document with viewport</text>
|
|
||||||
<text x="650" y="108" >begin/end group with attributes</text>
|
|
||||||
<text x="650" y="136" >begin/end group style</text>
|
|
||||||
<text x="650" y="164" >begin/end group transform</text>
|
|
||||||
<text x="650" y="192" >begin/end group id</text>
|
|
||||||
<text x="650" y="220" >begin/end clip path</text>
|
|
||||||
<text x="650" y="248" >begin/end a defintion block</text>
|
|
||||||
<text x="650" y="276" >begin/end markers</text>
|
|
||||||
<text x="650" y="304" >begin/end pattern</text>
|
|
||||||
<text x="650" y="332" >set the description element</text>
|
|
||||||
<text x="650" y="360" >set the title element</text>
|
|
||||||
<text x="650" y="388" >define a script</text>
|
|
||||||
<text x="650" y="416" >begin/end mask element</text>
|
|
||||||
<text x="650" y="444" >begin/end link to href, with a title</text>
|
|
||||||
<text x="650" y="472" >use defined objects</text>
|
|
||||||
</g>
|
|
||||||
<text x="500" y="534" style="fill:black; text-anchor:middle;font-size:24px">Textlines(x, y int, s []string, size, spacing int, fill, align string)</text>
|
|
||||||
</g>
|
|
||||||
<g id="rgb">
|
|
||||||
<g style="fill:gray; text-anchor:middle">
|
|
||||||
<circle cx="62" cy="0" r="15" style="fill:rgb(44,0,0)"/>
|
|
||||||
<circle cx="125" cy="0" r="15" style="fill:rgb(0,77,0)"/>
|
|
||||||
<circle cx="187" cy="0" r="15" style="fill:rgb(0,0,232)"/>
|
|
||||||
<circle cx="250" cy="0" r="15" style="fill:rgb(44,77,232)"/>
|
|
||||||
<text x="62" y="62" >r</text>
|
|
||||||
<text x="125" y="62" >g</text>
|
|
||||||
<text x="187" y="62" >b</text>
|
|
||||||
<text x="219" y="8" >-></text>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">RGB(r, g, b int)</text>
|
|
||||||
</g>
|
|
||||||
<g id="rgba">
|
|
||||||
<g style="fill:gray; text-anchor:middle">
|
|
||||||
<circle cx="62" cy="0" r="15" style="fill:rgb(44,0,0)"/>
|
|
||||||
<circle cx="125" cy="0" r="15" style="fill:rgb(0,77,0)"/>
|
|
||||||
<circle cx="187" cy="0" r="15" style="fill:rgb(0,0,232)"/>
|
|
||||||
<circle cx="250" cy="0" r="15" style="fill-opacity:0.33; fill:rgb(44,77,232)"/>
|
|
||||||
<circle cx="260" cy="0" r="15" style="fill-opacity:0.33; fill:rgb(44,77,232)"/>
|
|
||||||
<text x="250" y="62" >alpha</text>
|
|
||||||
<text x="62" y="62" >r</text>
|
|
||||||
<text x="125" y="62" >g</text>
|
|
||||||
<text x="187" y="62" >b</text>
|
|
||||||
<text x="219" y="8" >-></text>
|
|
||||||
</g>
|
|
||||||
<text x="125" y="149" style="fill:black; text-anchor:middle;font-size:24px">RGBA(r, g, b int, opacity float64)</text>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<title>SVG Go Library Description</title>
|
|
||||||
<rect x="0" y="0" width="4500" height="3375" style="fill:white;stroke:black;stroke-width:2"/>
|
|
||||||
<g style="font-family:Calibri,sans; text-anchor:middle; font-size:24px">
|
|
||||||
<a xlink:href="http://github.com/ajstarks/svgo" xlink:title="SVGo Library">
|
|
||||||
<text x="2250" y="150" style="font-size:125px">SVG Go Library</text>
|
|
||||||
<text x="2250" y="200" style="font-size:50px;fill:gray">github.com/ajstarks/svgo</text>
|
|
||||||
</a>
|
|
||||||
<desc>Object Usage</desc>
|
|
||||||
<g transform="translate(400,400)">
|
|
||||||
<use x="0" y="0" xlink:href="#rect" />
|
|
||||||
<use x="700" y="0" xlink:href="#crect" />
|
|
||||||
<use x="1400" y="0" xlink:href="#roundrect" />
|
|
||||||
<use x="2100" y="0" xlink:href="#square" />
|
|
||||||
<use x="2800" y="0" xlink:href="#line" />
|
|
||||||
<use x="3500" y="0" xlink:href="#polyline" />
|
|
||||||
</g>
|
|
||||||
<g transform="translate(400,1000)">
|
|
||||||
<use x="0" y="0" xlink:href="#polygon" />
|
|
||||||
<use x="700" y="0" xlink:href="#circle" />
|
|
||||||
<use x="1400" y="0" xlink:href="#ellipse" />
|
|
||||||
<use x="2100" y="0" xlink:href="#arc" />
|
|
||||||
<use x="2800" y="0" xlink:href="#qbez" />
|
|
||||||
<use x="3500" y="0" xlink:href="#bezier" />
|
|
||||||
</g>
|
|
||||||
<g transform="translate(400,1600)">
|
|
||||||
<use x="0" y="0" xlink:href="#trans" />
|
|
||||||
<use x="700" y="0" xlink:href="#scale" />
|
|
||||||
<use x="1400" y="0" xlink:href="#scalexy" />
|
|
||||||
<use x="2100" y="0" xlink:href="#skewx" />
|
|
||||||
<use x="2800" y="0" xlink:href="#skewy" />
|
|
||||||
<use x="3500" y="0" xlink:href="#skewxy" />
|
|
||||||
</g>
|
|
||||||
<g transform="translate(400,2200)">
|
|
||||||
<use x="0" y="0" xlink:href="#rotate" />
|
|
||||||
<use x="700" y="0" xlink:href="#text" />
|
|
||||||
<use x="1400" y="0" xlink:href="#textpath" />
|
|
||||||
<use x="2100" y="0" xlink:href="#path" />
|
|
||||||
<use x="2800" y="0" xlink:href="#image" />
|
|
||||||
<use x="3500" y="0" xlink:href="#grid" />
|
|
||||||
</g>
|
|
||||||
<g transform="translate(400,2800)">
|
|
||||||
<use x="0" y="0" xlink:href="#lgrad" />
|
|
||||||
<use x="700" y="0" xlink:href="#rgrad" />
|
|
||||||
<use x="1400" y="0" xlink:href="#rgb" />
|
|
||||||
<use x="2100" y="0" xlink:href="#rgba" />
|
|
||||||
<use x="2800" y="0" xlink:href="#meta" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 21 KiB |
4
vendor/github.com/campoy/embedmd/.gitignore
generated
vendored
4
vendor/github.com/campoy/embedmd/.gitignore
generated
vendored
@@ -1,4 +0,0 @@
|
|||||||
.vscode/
|
|
||||||
*.test
|
|
||||||
embed
|
|
||||||
.DS_Store
|
|
||||||
9
vendor/github.com/campoy/embedmd/.travis.yml
generated
vendored
9
vendor/github.com/campoy/embedmd/.travis.yml
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
language: go
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.x
|
|
||||||
- master
|
|
||||||
script:
|
|
||||||
- go test ./...
|
|
||||||
202
vendor/github.com/campoy/embedmd/LICENSE
generated
vendored
202
vendor/github.com/campoy/embedmd/LICENSE
generated
vendored
@@ -1,202 +0,0 @@
|
|||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
APPENDIX: How to apply the Apache License to your work.
|
|
||||||
|
|
||||||
To apply the Apache License to your work, attach the following
|
|
||||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
|
||||||
replaced with your own identifying information. (Don't include
|
|
||||||
the brackets!) The text should be enclosed in the appropriate
|
|
||||||
comment syntax for the file format. We also recommend that a
|
|
||||||
file or class name and description of purpose be included on the
|
|
||||||
same "printed page" as the copyright notice for easier
|
|
||||||
identification within third-party archives.
|
|
||||||
|
|
||||||
Copyright {yyyy} {name of copyright owner}
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
|
|
||||||
152
vendor/github.com/campoy/embedmd/README.md
generated
vendored
152
vendor/github.com/campoy/embedmd/README.md
generated
vendored
@@ -1,152 +0,0 @@
|
|||||||
[](https://travis-ci.org/campoy/embedmd) [](https://goreportcard.com/report/github.com/campoy/embedmd)
|
|
||||||
|
|
||||||
|
|
||||||
# embedmd
|
|
||||||
|
|
||||||
Are you tired of copy pasting your code into your `README.md` file, just to
|
|
||||||
forget about it later on and have unsynced copies? Or even worse, code
|
|
||||||
that does not even compile?
|
|
||||||
|
|
||||||
Then `embedmd` is for you!
|
|
||||||
|
|
||||||
`embedmd` embeds files or fractions of files into Markdown files. It does
|
|
||||||
so by searching `embedmd` commands, which are a subset of the Markdown
|
|
||||||
syntax for comments. This means they are invisible when Markdown is
|
|
||||||
rendered, so they can be kept in the file as pointers to the origin of
|
|
||||||
the embedded text.
|
|
||||||
|
|
||||||
The command receives a list of Markdown files. If no list is given, the command
|
|
||||||
reads from the standard input.
|
|
||||||
|
|
||||||
The format of an `embedmd` command is:
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (pathOrURL language /start regexp/ /end regexp/)
|
|
||||||
```
|
|
||||||
|
|
||||||
The embedded code will be extracted from the file at `pathOrURL`,
|
|
||||||
which can either be a relative path to a file in the local file
|
|
||||||
system (using always forward slashes as directory separator) or
|
|
||||||
a URL starting with `http://` or `https://`.
|
|
||||||
If the `pathOrURL` is a URL the tool will fetch the content in that URL.
|
|
||||||
The embedded content starts at the first line that matches `/start regexp/`
|
|
||||||
and finishes at the first line matching `/end regexp/`.
|
|
||||||
|
|
||||||
Omitting the the second regular expression will embed only the piece of text
|
|
||||||
that matches `/regexp/`:
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (pathOrURL language /regexp/)
|
|
||||||
```
|
|
||||||
|
|
||||||
To embed the whole line matching a regular expression you can use:
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (pathOrURL language /.*regexp.*/)
|
|
||||||
```
|
|
||||||
|
|
||||||
To embed from a point to the end you should use:
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (pathOrURL language /start regexp/ $)
|
|
||||||
```
|
|
||||||
|
|
||||||
To embed a whole file, omit both regular expressions:
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (pathOrURL language)
|
|
||||||
```
|
|
||||||
|
|
||||||
You can omit the language in any of the previous commands, and the extension
|
|
||||||
of the file will be used for the snippet syntax highlighting.
|
|
||||||
|
|
||||||
This works when the file extensions matches the name of the language (like Go
|
|
||||||
files, since `.go` matches `go`). However, this will fail with other files like
|
|
||||||
`.md` whose language name is `markdown`.
|
|
||||||
|
|
||||||
```Markdown
|
|
||||||
[embedmd]:# (file.ext)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
> You can install Go by following [these instructions](https://golang.org/doc/install).
|
|
||||||
|
|
||||||
`embedmd` is written in Go, so if you have Go installed you can install it with
|
|
||||||
`go get`:
|
|
||||||
|
|
||||||
```
|
|
||||||
go get github.com/campoy/embedmd
|
|
||||||
```
|
|
||||||
|
|
||||||
This will download the code, compile it, and leave an `embedmd` binary
|
|
||||||
in `$GOPATH/bin`.
|
|
||||||
|
|
||||||
Eventually, and if there's enough interest, I will provide binaries for
|
|
||||||
every OS and architecture out there ... _eventually_.
|
|
||||||
|
|
||||||
## Usage:
|
|
||||||
|
|
||||||
Given the two files in [sample](sample):
|
|
||||||
|
|
||||||
*hello.go:*
|
|
||||||
|
|
||||||
[embedmd]:# (sample/hello.go)
|
|
||||||
```go
|
|
||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Use of this source code is governed by the Apache 2.0
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
fmt.Println("Hello, there, it is", time.Now())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
*docs.md:*
|
|
||||||
|
|
||||||
[embedmd]:# (sample/docs.md Markdown /./ /embedmd.*time.*/)
|
|
||||||
```Markdown
|
|
||||||
# A hello world in Go
|
|
||||||
|
|
||||||
Go is very simple, here you can see a whole "hello, world" program.
|
|
||||||
|
|
||||||
[embedmd]:# (hello.go)
|
|
||||||
|
|
||||||
We can try to embed a file from a directory.
|
|
||||||
|
|
||||||
[embedmd]:# (test/hello.go /func main/ $)
|
|
||||||
|
|
||||||
You always start with a `package` statement like:
|
|
||||||
|
|
||||||
[embedmd]:# (hello.go /package.*/)
|
|
||||||
|
|
||||||
Followed by an `import` statement:
|
|
||||||
|
|
||||||
[embedmd]:# (hello.go /import/ /\)/)
|
|
||||||
|
|
||||||
You can also see how to get the current time:
|
|
||||||
|
|
||||||
[embedmd]:# (hello.go /time\.[^)]*\)/)
|
|
||||||
```
|
|
||||||
|
|
||||||
# Flags
|
|
||||||
|
|
||||||
* `-w`: Executing `embedmd -w docs.md` will modify `docs.md`
|
|
||||||
and add the corresponding code snippets, as shown in
|
|
||||||
[sample/result.md](sample/result.md).
|
|
||||||
|
|
||||||
* `-d`: Executing `embedmd -d docs.md` will display the difference
|
|
||||||
between the contents of `docs.md` and the output of
|
|
||||||
`embedmd docs.md`.
|
|
||||||
|
|
||||||
### Disclaimer
|
|
||||||
|
|
||||||
This is not an official Google product (experimental or otherwise), it is just
|
|
||||||
code that happens to be owned by Google.
|
|
||||||
101
vendor/github.com/campoy/embedmd/embedmd/command.go
generated
vendored
101
vendor/github.com/campoy/embedmd/embedmd/command.go
generated
vendored
@@ -1,101 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to writing, software distributed
|
|
||||||
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package embedmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type command struct {
|
|
||||||
path, lang string
|
|
||||||
start, end *string
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseCommand(s string) (*command, error) {
|
|
||||||
s = strings.TrimSpace(s)
|
|
||||||
if len(s) < 2 || s[0] != '(' || s[len(s)-1] != ')' {
|
|
||||||
return nil, errors.New("argument list should be in parenthesis")
|
|
||||||
}
|
|
||||||
|
|
||||||
args, err := fields(s[1 : len(s)-1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(args) == 0 {
|
|
||||||
return nil, errors.New("missing file name")
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd := &command{path: args[0]}
|
|
||||||
args = args[1:]
|
|
||||||
if len(args) > 0 && args[0][0] != '/' {
|
|
||||||
cmd.lang, args = args[0], args[1:]
|
|
||||||
} else {
|
|
||||||
ext := filepath.Ext(cmd.path[1:])
|
|
||||||
if len(ext) == 0 {
|
|
||||||
return nil, errors.New("language is required when file has no extension")
|
|
||||||
}
|
|
||||||
cmd.lang = ext[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case len(args) == 1:
|
|
||||||
cmd.start = &args[0]
|
|
||||||
case len(args) == 2:
|
|
||||||
cmd.start, cmd.end = &args[0], &args[1]
|
|
||||||
case len(args) > 2:
|
|
||||||
return nil, errors.New("too many arguments")
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// fields returns a list of the groups of text separated by blanks,
|
|
||||||
// keeping all text surrounded by / as a group.
|
|
||||||
func fields(s string) ([]string, error) {
|
|
||||||
var args []string
|
|
||||||
|
|
||||||
for s = strings.TrimSpace(s); len(s) > 0; s = strings.TrimSpace(s) {
|
|
||||||
if s[0] == '/' {
|
|
||||||
sep := nextSlash(s[1:])
|
|
||||||
if sep < 0 {
|
|
||||||
return nil, errors.New("unbalanced /")
|
|
||||||
}
|
|
||||||
args, s = append(args, s[:sep+2]), s[sep+2:]
|
|
||||||
} else {
|
|
||||||
sep := strings.IndexByte(s[1:], ' ')
|
|
||||||
if sep < 0 {
|
|
||||||
return append(args, s), nil
|
|
||||||
}
|
|
||||||
args, s = append(args, s[:sep+1]), s[sep+1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// nextSlash will find the index of the next unescaped slash in a string.
|
|
||||||
func nextSlash(s string) int {
|
|
||||||
for sep := 0; ; sep++ {
|
|
||||||
i := strings.IndexByte(s[sep:], '/')
|
|
||||||
if i < 0 {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
sep += i
|
|
||||||
if sep == 0 || s[sep-1] != '\\' {
|
|
||||||
return sep
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
51
vendor/github.com/campoy/embedmd/embedmd/content.go
generated
vendored
51
vendor/github.com/campoy/embedmd/embedmd/content.go
generated
vendored
@@ -1,51 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to writing, software distributed
|
|
||||||
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package embedmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Fetcher provides an abstraction on a file system.
|
|
||||||
// The Fetch function is called anytime some content needs to be fetched.
|
|
||||||
// For now this includes files and URLs.
|
|
||||||
// The first parameter is the base directory that could be used to resolve
|
|
||||||
// relative paths. This base directory will be ignored for absolute paths,
|
|
||||||
// such as URLs.
|
|
||||||
type Fetcher interface {
|
|
||||||
Fetch(dir, path string) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type fetcher struct{}
|
|
||||||
|
|
||||||
func (fetcher) Fetch(dir, path string) ([]byte, error) {
|
|
||||||
if !strings.HasPrefix(path, "http://") && !strings.HasPrefix(path, "https://") {
|
|
||||||
path = filepath.Join(dir, filepath.FromSlash(path))
|
|
||||||
return ioutil.ReadFile(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := http.Get(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer res.Body.Close()
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
return nil, fmt.Errorf("status %s", res.Status)
|
|
||||||
}
|
|
||||||
return ioutil.ReadAll(res.Body)
|
|
||||||
}
|
|
||||||
153
vendor/github.com/campoy/embedmd/embedmd/embedmd.go
generated
vendored
153
vendor/github.com/campoy/embedmd/embedmd/embedmd.go
generated
vendored
@@ -1,153 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to writing, software distributed
|
|
||||||
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// Package embedmd provides a single function, Process, that parses markdown
|
|
||||||
// searching for markdown comments.
|
|
||||||
//
|
|
||||||
// The format of an embedmd command is:
|
|
||||||
//
|
|
||||||
// [embedmd]:# (pathOrURL language /start regexp/ /end regexp/)
|
|
||||||
//
|
|
||||||
// The embedded code will be extracted from the file at pathOrURL,
|
|
||||||
// which can either be a relative path to a file in the local file
|
|
||||||
// system (using always forward slashes as directory separator) or
|
|
||||||
// a url starting with http:// or https://.
|
|
||||||
// If the pathOrURL is a url the tool will fetch the content in that url.
|
|
||||||
// The embedded content starts at the first line that matches /start regexp/
|
|
||||||
// and finishes at the first line matching /end regexp/.
|
|
||||||
//
|
|
||||||
// Omitting the the second regular expression will embed only the piece of
|
|
||||||
// text that matches /regexp/:
|
|
||||||
//
|
|
||||||
// [embedmd]:# (pathOrURL language /regexp/)
|
|
||||||
//
|
|
||||||
// To embed the whole line matching a regular expression you can use:
|
|
||||||
//
|
|
||||||
// [embedmd]:# (pathOrURL language /.*regexp.*\n/)
|
|
||||||
//
|
|
||||||
// If you want to embed from a point to the end you should use:
|
|
||||||
//
|
|
||||||
// [embedmd]:# (pathOrURL language /start regexp/ $)
|
|
||||||
//
|
|
||||||
// Finally you can embed a whole file by omitting both regular expressions:
|
|
||||||
//
|
|
||||||
// [embedmd]:# (pathOrURL language)
|
|
||||||
//
|
|
||||||
// You can ommit the language in any of the previous commands, and the extension
|
|
||||||
// of the file will be used for the snippet syntax highlighting. Note that while
|
|
||||||
// this works Go files, since the file extension .go matches the name of the language
|
|
||||||
// go, this will fail with other files like .md whose language name is markdown.
|
|
||||||
//
|
|
||||||
// [embedmd]:# (file.ext)
|
|
||||||
//
|
|
||||||
package embedmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Process reads markdown from the given io.Reader searching for an embedmd
|
|
||||||
// command. When a command is found, it is executed and the output is written
|
|
||||||
// into the given io.Writer with the rest of standard markdown.
|
|
||||||
func Process(out io.Writer, in io.Reader, opts ...Option) error {
|
|
||||||
e := embedder{Fetcher: fetcher{}}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt.f(&e)
|
|
||||||
}
|
|
||||||
return process(out, in, e.runCommand)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An Option provides a way to adapt the Process function to your needs.
|
|
||||||
type Option struct{ f func(*embedder) }
|
|
||||||
|
|
||||||
// WithBaseDir indicates that the given path should be used to resolve relative
|
|
||||||
// paths.
|
|
||||||
func WithBaseDir(path string) Option {
|
|
||||||
return Option{func(e *embedder) { e.baseDir = path }}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithFetcher provides a custom Fetcher to be used whenever a path or url needs
|
|
||||||
// to be fetched.
|
|
||||||
func WithFetcher(c Fetcher) Option {
|
|
||||||
return Option{func(e *embedder) { e.Fetcher = c }}
|
|
||||||
}
|
|
||||||
|
|
||||||
type embedder struct {
|
|
||||||
Fetcher
|
|
||||||
baseDir string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *embedder) runCommand(w io.Writer, cmd *command) error {
|
|
||||||
b, err := e.Fetch(e.baseDir, cmd.path)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not read %s: %v", cmd.path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err = extract(b, cmd.start, cmd.end)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not extract content from %s: %v", cmd.path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(b) > 0 && b[len(b)-1] != '\n' {
|
|
||||||
b = append(b, '\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Fprintln(w, "```"+cmd.lang)
|
|
||||||
w.Write(b)
|
|
||||||
fmt.Fprintln(w, "```")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func extract(b []byte, start, end *string) ([]byte, error) {
|
|
||||||
if start == nil && end == nil {
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
match := func(s string) ([]int, error) {
|
|
||||||
if len(s) <= 2 || s[0] != '/' || s[len(s)-1] != '/' {
|
|
||||||
return nil, fmt.Errorf("missing slashes (/) around %q", s)
|
|
||||||
}
|
|
||||||
re, err := regexp.CompilePOSIX(s[1 : len(s)-1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
loc := re.FindIndex(b)
|
|
||||||
if loc == nil {
|
|
||||||
return nil, fmt.Errorf("could not match %q", s)
|
|
||||||
}
|
|
||||||
return loc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if *start != "" {
|
|
||||||
loc, err := match(*start)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if end == nil {
|
|
||||||
return b[loc[0]:loc[1]], nil
|
|
||||||
}
|
|
||||||
b = b[loc[0]:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if *end != "$" {
|
|
||||||
loc, err := match(*end)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
b = b[:loc[1]]
|
|
||||||
}
|
|
||||||
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
117
vendor/github.com/campoy/embedmd/embedmd/parser.go
generated
vendored
117
vendor/github.com/campoy/embedmd/embedmd/parser.go
generated
vendored
@@ -1,117 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to writing, software distributed
|
|
||||||
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package embedmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type commandRunner func(io.Writer, *command) error
|
|
||||||
|
|
||||||
func process(out io.Writer, in io.Reader, run commandRunner) error {
|
|
||||||
s := &countingScanner{bufio.NewScanner(in), 0}
|
|
||||||
|
|
||||||
state := parsingText
|
|
||||||
var err error
|
|
||||||
for state != nil {
|
|
||||||
state, err = state(out, s, run)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%d: %v", s.line, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.Err(); err != nil {
|
|
||||||
return fmt.Errorf("%d: %v", s.line, err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type countingScanner struct {
|
|
||||||
*bufio.Scanner
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *countingScanner) Scan() bool {
|
|
||||||
b := c.Scanner.Scan()
|
|
||||||
if b {
|
|
||||||
c.line++
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
type textScanner interface {
|
|
||||||
Text() string
|
|
||||||
Scan() bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type state func(io.Writer, textScanner, commandRunner) (state, error)
|
|
||||||
|
|
||||||
func parsingText(out io.Writer, s textScanner, run commandRunner) (state, error) {
|
|
||||||
if !s.Scan() {
|
|
||||||
return nil, nil // end of file, which is fine.
|
|
||||||
}
|
|
||||||
switch line := s.Text(); {
|
|
||||||
case strings.HasPrefix(line, "[embedmd]:#"):
|
|
||||||
return parsingCmd, nil
|
|
||||||
case strings.HasPrefix(line, "```"):
|
|
||||||
return codeParser{print: true}.parse, nil
|
|
||||||
default:
|
|
||||||
fmt.Fprintln(out, s.Text())
|
|
||||||
return parsingText, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func parsingCmd(out io.Writer, s textScanner, run commandRunner) (state, error) {
|
|
||||||
line := s.Text()
|
|
||||||
fmt.Fprintln(out, line)
|
|
||||||
args := line[strings.Index(line, "#")+1:]
|
|
||||||
cmd, err := parseCommand(args)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := run(out, cmd); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if !s.Scan() {
|
|
||||||
return nil, nil // end of file, which is fine.
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(s.Text(), "```") {
|
|
||||||
return codeParser{print: false}.parse, nil
|
|
||||||
}
|
|
||||||
fmt.Fprintln(out, s.Text())
|
|
||||||
return parsingText, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type codeParser struct{ print bool }
|
|
||||||
|
|
||||||
func (c codeParser) parse(out io.Writer, s textScanner, run commandRunner) (state, error) {
|
|
||||||
if c.print {
|
|
||||||
fmt.Fprintln(out, s.Text())
|
|
||||||
}
|
|
||||||
if !s.Scan() {
|
|
||||||
return nil, fmt.Errorf("unbalanced code section")
|
|
||||||
}
|
|
||||||
if !strings.HasPrefix(s.Text(), "```") {
|
|
||||||
return c.parse, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// print the end of the code section if needed and go back to parsing text.
|
|
||||||
if c.print {
|
|
||||||
fmt.Fprintln(out, s.Text())
|
|
||||||
}
|
|
||||||
return parsingText, nil
|
|
||||||
}
|
|
||||||
185
vendor/github.com/campoy/embedmd/main.go
generated
vendored
185
vendor/github.com/campoy/embedmd/main.go
generated
vendored
@@ -1,185 +0,0 @@
|
|||||||
// Copyright 2016 Google Inc. All rights reserved.
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to writing, software distributed
|
|
||||||
// under the License is distributed on a "AS IS" BASIS, WITHOUT WARRANTIES OR
|
|
||||||
// CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
//
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
// embedmd
|
|
||||||
//
|
|
||||||
// embedmd embeds files or fractions of files into markdown files.
|
|
||||||
// It does so by searching embedmd commands, which are a subset of the
|
|
||||||
// markdown syntax for comments. This means they are invisible when
|
|
||||||
// markdown is rendered, so they can be kept in the file as pointers
|
|
||||||
// to the origin of the embedded text.
|
|
||||||
//
|
|
||||||
// The command receives a list of markdown files, if none is given it
|
|
||||||
// reads from the standard input.
|
|
||||||
//
|
|
||||||
// embedmd supports two flags:
|
|
||||||
// -d: will print the difference of the input file with what the output
|
|
||||||
// would have been if executed.
|
|
||||||
// -w: rewrites the given files rather than writing the output to the standard
|
|
||||||
// output.
|
|
||||||
//
|
|
||||||
// For more information on the format of the commands, read the documentation
|
|
||||||
// of the github.com/campoy/embedmd/embedmd package.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/campoy/embedmd/embedmd"
|
|
||||||
"github.com/pmezard/go-difflib/difflib"
|
|
||||||
)
|
|
||||||
|
|
||||||
// modified while building by -ldflags.
|
|
||||||
var version = "unkown"
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
fmt.Fprintf(os.Stderr, "usage: embedmd [flags] [path ...]\n")
|
|
||||||
flag.PrintDefaults()
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rewrite := flag.Bool("w", false, "write result to (markdown) file instead of stdout")
|
|
||||||
doDiff := flag.Bool("d", false, "display diffs instead of rewriting files")
|
|
||||||
printVersion := flag.Bool("v", false, "display embedmd version")
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
if *printVersion {
|
|
||||||
fmt.Println("embedmd version: " + version)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
diff, err := embed(flag.Args(), *rewrite, *doDiff)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
if diff && *doDiff {
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
stdout io.Writer = os.Stdout
|
|
||||||
stdin io.Reader = os.Stdin
|
|
||||||
)
|
|
||||||
|
|
||||||
func embed(paths []string, rewrite, doDiff bool) (foundDiff bool, err error) {
|
|
||||||
if rewrite && doDiff {
|
|
||||||
return false, fmt.Errorf("error: cannot use -w and -d simultaneously")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(paths) == 0 {
|
|
||||||
if rewrite {
|
|
||||||
return false, fmt.Errorf("error: cannot use -w with standard input")
|
|
||||||
}
|
|
||||||
if !doDiff {
|
|
||||||
return false, embedmd.Process(stdout, stdin)
|
|
||||||
}
|
|
||||||
|
|
||||||
var out, in bytes.Buffer
|
|
||||||
if err := embedmd.Process(&out, io.TeeReader(stdin, &in)); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
d, err := diff(in.String(), out.String())
|
|
||||||
if err != nil || len(d) == 0 {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(stdout, "%s", d)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range paths {
|
|
||||||
d, err := processFile(path, rewrite, doDiff)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("%s:%v", path, err)
|
|
||||||
}
|
|
||||||
foundDiff = foundDiff || d
|
|
||||||
}
|
|
||||||
return foundDiff, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type file interface {
|
|
||||||
io.ReadCloser
|
|
||||||
io.WriterAt
|
|
||||||
Truncate(int64) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// replaced by testing functions.
|
|
||||||
var openFile = func(name string) (file, error) {
|
|
||||||
return os.OpenFile(name, os.O_RDWR, 0666)
|
|
||||||
}
|
|
||||||
|
|
||||||
func readFile(path string) ([]byte, error) {
|
|
||||||
f, err := openFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
return ioutil.ReadAll(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
func processFile(path string, rewrite, doDiff bool) (foundDiff bool, err error) {
|
|
||||||
if filepath.Ext(path) != ".md" {
|
|
||||||
return false, fmt.Errorf("not a markdown file")
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := openFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if err := embedmd.Process(buf, f, embedmd.WithBaseDir(filepath.Dir(path))); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if doDiff {
|
|
||||||
f, err := readFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("could not read %s for diff: %v", path, err)
|
|
||||||
}
|
|
||||||
data, err := diff(string(f), buf.String())
|
|
||||||
if err != nil || len(data) == 0 {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
fmt.Fprintf(stdout, "%s", data)
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if rewrite {
|
|
||||||
n, err := f.WriteAt(buf.Bytes(), 0)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("could not write: %v", err)
|
|
||||||
}
|
|
||||||
return false, f.Truncate(int64(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
io.Copy(stdout, buf)
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func diff(a, b string) (string, error) {
|
|
||||||
return difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
|
|
||||||
A: difflib.SplitLines(a),
|
|
||||||
B: difflib.SplitLines(b),
|
|
||||||
Context: 3,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
21
vendor/github.com/duke-git/lancet/v2/LICENSE
generated
vendored
21
vendor/github.com/duke-git/lancet/v2/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 Beyond
|
|
||||||
|
|
||||||
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.
|
|
||||||
397
vendor/github.com/duke-git/lancet/v2/mathutil/mathutil.go
generated
vendored
397
vendor/github.com/duke-git/lancet/v2/mathutil/mathutil.go
generated
vendored
@@ -1,397 +0,0 @@
|
|||||||
// Copyright 2021 dudaodong@gmail.com. All rights reserved.
|
|
||||||
// Use of this source code is governed by MIT license
|
|
||||||
|
|
||||||
// Package mathutil implements some functions for math calculation.
|
|
||||||
package mathutil
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/exp/constraints"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Exponent calculate x^n.
|
|
||||||
// Play: https://go.dev/play/p/uF3HGNPk8wr
|
|
||||||
func Exponent(x, n int64) int64 {
|
|
||||||
if n == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
t := Exponent(x, n/2)
|
|
||||||
|
|
||||||
if n%2 == 1 {
|
|
||||||
return t * t * x
|
|
||||||
}
|
|
||||||
|
|
||||||
return t * t
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fibonacci calculate fibonacci number before n.
|
|
||||||
// Play: https://go.dev/play/p/IscseUNMuUc
|
|
||||||
func Fibonacci(first, second, n int) int {
|
|
||||||
if n <= 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if n < 3 {
|
|
||||||
return 1
|
|
||||||
} else if n == 3 {
|
|
||||||
return first + second
|
|
||||||
} else {
|
|
||||||
return Fibonacci(second, first+second, n-1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Factorial calculate x!.
|
|
||||||
// Play: https://go.dev/play/p/tt6LdOK67Nx
|
|
||||||
func Factorial(x uint) uint {
|
|
||||||
var f uint = 1
|
|
||||||
for ; x > 1; x-- {
|
|
||||||
f *= x
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Percent calculate the percentage of value to total.
|
|
||||||
// Play: https://go.dev/play/p/s0NdFCtwuyd
|
|
||||||
func Percent(val, total float64, n int) float64 {
|
|
||||||
if total == 0 {
|
|
||||||
return float64(0)
|
|
||||||
}
|
|
||||||
tmp := val / total * 100
|
|
||||||
result := RoundToFloat(tmp, n)
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundToString round off to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/kZwpBRAcllO
|
|
||||||
func RoundToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Round(float64(x))
|
|
||||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundToFloat round off to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/ghyb528JRJL
|
|
||||||
func RoundToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Round(float64(x))
|
|
||||||
return r / tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// TruncRound round off n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/aumarSHIGzP
|
|
||||||
func TruncRound[T constraints.Float | constraints.Integer](x T, n int) T {
|
|
||||||
floatStr := fmt.Sprintf("%."+strconv.Itoa(n+1)+"f", x)
|
|
||||||
temp := strings.Split(floatStr, ".")
|
|
||||||
var newFloat string
|
|
||||||
if len(temp) < 2 || n >= len(temp[1]) {
|
|
||||||
newFloat = floatStr
|
|
||||||
} else {
|
|
||||||
newFloat = temp[0] + "." + temp[1][:n]
|
|
||||||
}
|
|
||||||
result, _ := strconv.ParseFloat(newFloat, 64)
|
|
||||||
return T(result)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloorToFloat round down to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/vbCBrQHZEED
|
|
||||||
func FloorToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Floor(float64(x))
|
|
||||||
return r / tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// FloorToString round down to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/Qk9KPd2IdDb
|
|
||||||
func FloorToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Floor(float64(x))
|
|
||||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// CeilToFloat round up to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/8hOeSADZPCo
|
|
||||||
func CeilToFloat[T constraints.Float | constraints.Integer](x T, n int) float64 {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Ceil(float64(x))
|
|
||||||
return r / tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// CeilToString round up to n decimal places.
|
|
||||||
// Play: https://go.dev/play/p/wy5bYEyUKKG
|
|
||||||
func CeilToString[T constraints.Float | constraints.Integer](x T, n int) string {
|
|
||||||
tmp := math.Pow(10.0, float64(n))
|
|
||||||
x *= T(tmp)
|
|
||||||
r := math.Ceil(float64(x))
|
|
||||||
result := strconv.FormatFloat(r/tmp, 'f', n, 64)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Max return max value of numbers.
|
|
||||||
// Play: https://go.dev/play/p/cN8DHI0rTkH
|
|
||||||
func Max[T constraints.Integer | constraints.Float](numbers ...T) T {
|
|
||||||
max := numbers[0]
|
|
||||||
|
|
||||||
for _, v := range numbers {
|
|
||||||
if max < v {
|
|
||||||
max = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
// MaxBy return the maximum value of a slice using the given comparator function.
|
|
||||||
// Play: https://go.dev/play/p/pbe2MT-7DV2
|
|
||||||
func MaxBy[T any](slice []T, comparator func(T, T) bool) T {
|
|
||||||
var max T
|
|
||||||
|
|
||||||
if len(slice) == 0 {
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
max = slice[0]
|
|
||||||
|
|
||||||
for i := 1; i < len(slice); i++ {
|
|
||||||
val := slice[i]
|
|
||||||
|
|
||||||
if comparator(val, max) {
|
|
||||||
max = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return max
|
|
||||||
}
|
|
||||||
|
|
||||||
// Min return min value of numbers.
|
|
||||||
// Play: https://go.dev/play/p/21BER_mlGUj
|
|
||||||
func Min[T constraints.Integer | constraints.Float](numbers ...T) T {
|
|
||||||
min := numbers[0]
|
|
||||||
|
|
||||||
for _, v := range numbers {
|
|
||||||
if min > v {
|
|
||||||
min = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
// MinBy return the minimum value of a slice using the given comparator function.
|
|
||||||
// Play: https://go.dev/play/p/XuJDKrDdglW
|
|
||||||
func MinBy[T any](slice []T, comparator func(T, T) bool) T {
|
|
||||||
var min T
|
|
||||||
|
|
||||||
if len(slice) == 0 {
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
min = slice[0]
|
|
||||||
|
|
||||||
for i := 1; i < len(slice); i++ {
|
|
||||||
val := slice[i]
|
|
||||||
|
|
||||||
if comparator(val, min) {
|
|
||||||
min = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return min
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sum return sum of passed numbers.
|
|
||||||
// Play: https://go.dev/play/p/1To2ImAMJA7
|
|
||||||
func Sum[T constraints.Integer | constraints.Float](numbers ...T) T {
|
|
||||||
var sum T
|
|
||||||
|
|
||||||
for _, v := range numbers {
|
|
||||||
sum += v
|
|
||||||
}
|
|
||||||
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
|
|
||||||
// Average return average value of numbers.
|
|
||||||
// Play: https://go.dev/play/p/Vv7LBwER-pz
|
|
||||||
func Average[T constraints.Integer | constraints.Float](numbers ...T) T {
|
|
||||||
var sum T
|
|
||||||
n := T(len(numbers))
|
|
||||||
|
|
||||||
for _, v := range numbers {
|
|
||||||
sum += v
|
|
||||||
}
|
|
||||||
return sum / n
|
|
||||||
}
|
|
||||||
|
|
||||||
// Range creates a slice of numbers from start with specified count, element step is 1.
|
|
||||||
// Play: https://go.dev/play/p/9ke2opxa8ZP
|
|
||||||
func Range[T constraints.Integer | constraints.Float](start T, count int) []T {
|
|
||||||
size := count
|
|
||||||
if count < 0 {
|
|
||||||
size = -count
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]T, size)
|
|
||||||
|
|
||||||
for i, j := 0, start; i < size; i, j = i+1, j+1 {
|
|
||||||
result[i] = j
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// RangeWithStep creates a slice of numbers from start to end with specified step.
|
|
||||||
// Play: https://go.dev/play/p/akLWz0EqOSM
|
|
||||||
func RangeWithStep[T constraints.Integer | constraints.Float](start, end, step T) []T {
|
|
||||||
result := []T{}
|
|
||||||
|
|
||||||
if start >= end || step == 0 {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := start; i < end; i += step {
|
|
||||||
result = append(result, i)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// AngleToRadian converts angle value to radian value.
|
|
||||||
// Play: https://go.dev/play/p/CIvlICqrHql
|
|
||||||
func AngleToRadian(angle float64) float64 {
|
|
||||||
radian := angle * (math.Pi / 180)
|
|
||||||
return radian
|
|
||||||
}
|
|
||||||
|
|
||||||
// RadianToAngle converts radian value to angle value.
|
|
||||||
// Play: https://go.dev/play/p/dQtmOTUOMgi
|
|
||||||
func RadianToAngle(radian float64) float64 {
|
|
||||||
angle := radian * (180 / math.Pi)
|
|
||||||
return angle
|
|
||||||
}
|
|
||||||
|
|
||||||
// PointDistance get two points distance.
|
|
||||||
// Play: https://go.dev/play/p/RrG4JIaziM8
|
|
||||||
func PointDistance(x1, y1, x2, y2 float64) float64 {
|
|
||||||
a := x1 - x2
|
|
||||||
b := y1 - y2
|
|
||||||
c := math.Pow(a, 2) + math.Pow(b, 2)
|
|
||||||
|
|
||||||
return math.Sqrt(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrime checks if number is prime number.
|
|
||||||
// Play: https://go.dev/play/p/Rdd8UTHZJ7u
|
|
||||||
func IsPrime(n int) bool {
|
|
||||||
if n < 2 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 2; i <= int(math.Sqrt(float64(n))); i++ {
|
|
||||||
if n%i == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// GCD return greatest common divisor (GCD) of integers.
|
|
||||||
// Play: https://go.dev/play/p/CiEceLSoAKB
|
|
||||||
func GCD[T constraints.Integer](integers ...T) T {
|
|
||||||
result := integers[0]
|
|
||||||
|
|
||||||
for k := range integers {
|
|
||||||
result = gcd(integers[k], result)
|
|
||||||
|
|
||||||
if result == 1 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// find greatest common divisor (GCD)
|
|
||||||
func gcd[T constraints.Integer](a, b T) T {
|
|
||||||
if b == 0 {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
|
|
||||||
return gcd(b, a%b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// LCM return Least Common Multiple (LCM) of integers.
|
|
||||||
// Play: https://go.dev/play/p/EjcZxfY7G_g
|
|
||||||
func LCM[T constraints.Integer](integers ...T) T {
|
|
||||||
result := integers[0]
|
|
||||||
|
|
||||||
for k := range integers {
|
|
||||||
result = lcm(integers[k], result)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// find Least Common Multiple (LCM) via GCD.
|
|
||||||
func lcm[T constraints.Integer](a, b T) T {
|
|
||||||
if a == 0 || b == 0 {
|
|
||||||
panic("lcm function: provide non zero integers only.")
|
|
||||||
}
|
|
||||||
return a * b / gcd(a, b)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cos returns the cosine of the radian argument.
|
|
||||||
// Play: https://go.dev/play/p/Sm89LoIfvFq
|
|
||||||
func Cos(radian float64, precision ...int) float64 {
|
|
||||||
t := 1.0 / (2.0 * math.Pi)
|
|
||||||
radian *= t
|
|
||||||
radian -= 0.25 + math.Floor(radian+0.25)
|
|
||||||
radian *= 16.0 * (math.Abs(radian) - 0.5)
|
|
||||||
radian += 0.225 * radian * (math.Abs(radian) - 1.0)
|
|
||||||
|
|
||||||
if len(precision) == 1 {
|
|
||||||
return TruncRound(radian, precision[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
return TruncRound(radian, 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sin returns the sine of the radian argument.
|
|
||||||
// Play: https://go.dev/play/p/TWMQlMywDsP
|
|
||||||
func Sin(radian float64, precision ...int) float64 {
|
|
||||||
return Cos((math.Pi/2)-radian, precision...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Log returns the logarithm of base n.
|
|
||||||
// Play: https://go.dev/play/p/_d4bi8oyhat
|
|
||||||
func Log(n, base float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(base)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Abs returns the absolute value of x.
|
|
||||||
// Play: https://go.dev/play/p/fsyBh1Os-1d
|
|
||||||
func Abs[T constraints.Integer | constraints.Float](x T) T {
|
|
||||||
if x < 0 {
|
|
||||||
return (-x)
|
|
||||||
}
|
|
||||||
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
// Div returns the result of x divided by y.
|
|
||||||
// Play: https://go.dev/play/p/WLxDdGXXYat
|
|
||||||
func Div[T constraints.Float | constraints.Integer](x T, y T) float64 {
|
|
||||||
return float64(x) / float64(y)
|
|
||||||
}
|
|
||||||
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go_import_path: github.com/dustin/go-humanize
|
|
||||||
go:
|
|
||||||
- 1.13.x
|
|
||||||
- 1.14.x
|
|
||||||
- 1.15.x
|
|
||||||
- 1.16.x
|
|
||||||
- stable
|
|
||||||
- master
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: master
|
|
||||||
fast_finish: true
|
|
||||||
install:
|
|
||||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
|
||||||
script:
|
|
||||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
|
||||||
- go vet .
|
|
||||||
- go install -v -race ./...
|
|
||||||
- go test -v -race ./...
|
|
||||||
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
@@ -1,21 +0,0 @@
|
|||||||
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
<http://www.opensource.org/licenses/mit-license.php>
|
|
||||||
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
@@ -1,124 +0,0 @@
|
|||||||
# Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize)
|
|
||||||
|
|
||||||
Just a few functions for helping humanize times and sizes.
|
|
||||||
|
|
||||||
`go get` it as `github.com/dustin/go-humanize`, import it as
|
|
||||||
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
|
||||||
|
|
||||||
See [godoc](https://pkg.go.dev/github.com/dustin/go-humanize) for
|
|
||||||
complete documentation.
|
|
||||||
|
|
||||||
## Sizes
|
|
||||||
|
|
||||||
This lets you take numbers like `82854982` and convert them to useful
|
|
||||||
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Times
|
|
||||||
|
|
||||||
This lets you take a `time.Time` and spit it out in relative terms.
|
|
||||||
For example, `12 seconds ago` or `3 days from now`.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
|
||||||
```
|
|
||||||
|
|
||||||
Thanks to Kyle Lemons for the time implementation from an IRC
|
|
||||||
conversation one day. It's pretty neat.
|
|
||||||
|
|
||||||
## Ordinals
|
|
||||||
|
|
||||||
From a [mailing list discussion][odisc] where a user wanted to be able
|
|
||||||
to label ordinals.
|
|
||||||
|
|
||||||
0 -> 0th
|
|
||||||
1 -> 1st
|
|
||||||
2 -> 2nd
|
|
||||||
3 -> 3rd
|
|
||||||
4 -> 4th
|
|
||||||
[...]
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commas
|
|
||||||
|
|
||||||
Want to shove commas into numbers? Be my guest.
|
|
||||||
|
|
||||||
0 -> 0
|
|
||||||
100 -> 100
|
|
||||||
1000 -> 1,000
|
|
||||||
1000000000 -> 1,000,000,000
|
|
||||||
-100000 -> -100,000
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
|
||||||
```
|
|
||||||
|
|
||||||
## Ftoa
|
|
||||||
|
|
||||||
Nicer float64 formatter that removes trailing zeros.
|
|
||||||
|
|
||||||
```go
|
|
||||||
fmt.Printf("%f", 2.24) // 2.240000
|
|
||||||
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
|
||||||
fmt.Printf("%f", 2.0) // 2.000000
|
|
||||||
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
|
||||||
```
|
|
||||||
|
|
||||||
## SI notation
|
|
||||||
|
|
||||||
Format numbers with [SI notation][sinotation].
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
humanize.SI(0.00000000223, "M") // 2.23 nM
|
|
||||||
```
|
|
||||||
|
|
||||||
## English-specific functions
|
|
||||||
|
|
||||||
The following functions are in the `humanize/english` subpackage.
|
|
||||||
|
|
||||||
### Plurals
|
|
||||||
|
|
||||||
Simple English pluralization
|
|
||||||
|
|
||||||
```go
|
|
||||||
english.PluralWord(1, "object", "") // object
|
|
||||||
english.PluralWord(42, "object", "") // objects
|
|
||||||
english.PluralWord(2, "bus", "") // buses
|
|
||||||
english.PluralWord(99, "locus", "loci") // loci
|
|
||||||
|
|
||||||
english.Plural(1, "object", "") // 1 object
|
|
||||||
english.Plural(42, "object", "") // 42 objects
|
|
||||||
english.Plural(2, "bus", "") // 2 buses
|
|
||||||
english.Plural(99, "locus", "loci") // 99 loci
|
|
||||||
```
|
|
||||||
|
|
||||||
### Word series
|
|
||||||
|
|
||||||
Format comma-separated words lists with conjuctions:
|
|
||||||
|
|
||||||
```go
|
|
||||||
english.WordSeries([]string{"foo"}, "and") // foo
|
|
||||||
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
|
||||||
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
|
||||||
|
|
||||||
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
|
||||||
```
|
|
||||||
|
|
||||||
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
|
||||||
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
|
||||||
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
@@ -1,31 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// order of magnitude (to a max order)
|
|
||||||
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
|
||||||
mag := 0
|
|
||||||
m := &big.Int{}
|
|
||||||
for n.Cmp(b) >= 0 {
|
|
||||||
n.DivMod(n, b, m)
|
|
||||||
mag++
|
|
||||||
if mag == maxmag && maxmag >= 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
|
||||||
}
|
|
||||||
|
|
||||||
// total order of magnitude
|
|
||||||
// (same as above, but with no upper limit)
|
|
||||||
func oom(n, b *big.Int) (float64, int) {
|
|
||||||
mag := 0
|
|
||||||
m := &big.Int{}
|
|
||||||
for n.Cmp(b) >= 0 {
|
|
||||||
n.DivMod(n, b, m)
|
|
||||||
mag++
|
|
||||||
}
|
|
||||||
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
|
||||||
}
|
|
||||||
189
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
189
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
@@ -1,189 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bigIECExp = big.NewInt(1024)
|
|
||||||
|
|
||||||
// BigByte is one byte in bit.Ints
|
|
||||||
BigByte = big.NewInt(1)
|
|
||||||
// BigKiByte is 1,024 bytes in bit.Ints
|
|
||||||
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
|
||||||
// BigMiByte is 1,024 k bytes in bit.Ints
|
|
||||||
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
|
||||||
// BigGiByte is 1,024 m bytes in bit.Ints
|
|
||||||
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
|
||||||
// BigTiByte is 1,024 g bytes in bit.Ints
|
|
||||||
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
|
||||||
// BigPiByte is 1,024 t bytes in bit.Ints
|
|
||||||
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
|
||||||
// BigEiByte is 1,024 p bytes in bit.Ints
|
|
||||||
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
|
||||||
// BigZiByte is 1,024 e bytes in bit.Ints
|
|
||||||
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
|
||||||
// BigYiByte is 1,024 z bytes in bit.Ints
|
|
||||||
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
|
||||||
// BigRiByte is 1,024 y bytes in bit.Ints
|
|
||||||
BigRiByte = (&big.Int{}).Mul(BigYiByte, bigIECExp)
|
|
||||||
// BigQiByte is 1,024 r bytes in bit.Ints
|
|
||||||
BigQiByte = (&big.Int{}).Mul(BigRiByte, bigIECExp)
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bigSIExp = big.NewInt(1000)
|
|
||||||
|
|
||||||
// BigSIByte is one SI byte in big.Ints
|
|
||||||
BigSIByte = big.NewInt(1)
|
|
||||||
// BigKByte is 1,000 SI bytes in big.Ints
|
|
||||||
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
|
||||||
// BigMByte is 1,000 SI k bytes in big.Ints
|
|
||||||
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
|
||||||
// BigGByte is 1,000 SI m bytes in big.Ints
|
|
||||||
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
|
||||||
// BigTByte is 1,000 SI g bytes in big.Ints
|
|
||||||
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
|
||||||
// BigPByte is 1,000 SI t bytes in big.Ints
|
|
||||||
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
|
||||||
// BigEByte is 1,000 SI p bytes in big.Ints
|
|
||||||
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
|
||||||
// BigZByte is 1,000 SI e bytes in big.Ints
|
|
||||||
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
|
||||||
// BigYByte is 1,000 SI z bytes in big.Ints
|
|
||||||
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
|
||||||
// BigRByte is 1,000 SI y bytes in big.Ints
|
|
||||||
BigRByte = (&big.Int{}).Mul(BigYByte, bigSIExp)
|
|
||||||
// BigQByte is 1,000 SI r bytes in big.Ints
|
|
||||||
BigQByte = (&big.Int{}).Mul(BigRByte, bigSIExp)
|
|
||||||
)
|
|
||||||
|
|
||||||
var bigBytesSizeTable = map[string]*big.Int{
|
|
||||||
"b": BigByte,
|
|
||||||
"kib": BigKiByte,
|
|
||||||
"kb": BigKByte,
|
|
||||||
"mib": BigMiByte,
|
|
||||||
"mb": BigMByte,
|
|
||||||
"gib": BigGiByte,
|
|
||||||
"gb": BigGByte,
|
|
||||||
"tib": BigTiByte,
|
|
||||||
"tb": BigTByte,
|
|
||||||
"pib": BigPiByte,
|
|
||||||
"pb": BigPByte,
|
|
||||||
"eib": BigEiByte,
|
|
||||||
"eb": BigEByte,
|
|
||||||
"zib": BigZiByte,
|
|
||||||
"zb": BigZByte,
|
|
||||||
"yib": BigYiByte,
|
|
||||||
"yb": BigYByte,
|
|
||||||
"rib": BigRiByte,
|
|
||||||
"rb": BigRByte,
|
|
||||||
"qib": BigQiByte,
|
|
||||||
"qb": BigQByte,
|
|
||||||
// Without suffix
|
|
||||||
"": BigByte,
|
|
||||||
"ki": BigKiByte,
|
|
||||||
"k": BigKByte,
|
|
||||||
"mi": BigMiByte,
|
|
||||||
"m": BigMByte,
|
|
||||||
"gi": BigGiByte,
|
|
||||||
"g": BigGByte,
|
|
||||||
"ti": BigTiByte,
|
|
||||||
"t": BigTByte,
|
|
||||||
"pi": BigPiByte,
|
|
||||||
"p": BigPByte,
|
|
||||||
"ei": BigEiByte,
|
|
||||||
"e": BigEByte,
|
|
||||||
"z": BigZByte,
|
|
||||||
"zi": BigZiByte,
|
|
||||||
"y": BigYByte,
|
|
||||||
"yi": BigYiByte,
|
|
||||||
"r": BigRByte,
|
|
||||||
"ri": BigRiByte,
|
|
||||||
"q": BigQByte,
|
|
||||||
"qi": BigQiByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ten = big.NewInt(10)
|
|
||||||
|
|
||||||
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
|
||||||
if s.Cmp(ten) < 0 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
c := (&big.Int{}).Set(s)
|
|
||||||
val, mag := oomm(c, base, len(sizes)-1)
|
|
||||||
suffix := sizes[mag]
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// BigBytes produces a human readable representation of an SI size.
|
|
||||||
//
|
|
||||||
// See also: ParseBigBytes.
|
|
||||||
//
|
|
||||||
// BigBytes(82854982) -> 83 MB
|
|
||||||
func BigBytes(s *big.Int) string {
|
|
||||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "RB", "QB"}
|
|
||||||
return humanateBigBytes(s, bigSIExp, sizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BigIBytes produces a human readable representation of an IEC size.
|
|
||||||
//
|
|
||||||
// See also: ParseBigBytes.
|
|
||||||
//
|
|
||||||
// BigIBytes(82854982) -> 79 MiB
|
|
||||||
func BigIBytes(s *big.Int) string {
|
|
||||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB"}
|
|
||||||
return humanateBigBytes(s, bigIECExp, sizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBigBytes parses a string representation of bytes into the number
|
|
||||||
// of bytes it represents.
|
|
||||||
//
|
|
||||||
// See also: BigBytes, BigIBytes.
|
|
||||||
//
|
|
||||||
// ParseBigBytes("42 MB") -> 42000000, nil
|
|
||||||
// ParseBigBytes("42 mib") -> 44040192, nil
|
|
||||||
func ParseBigBytes(s string) (*big.Int, error) {
|
|
||||||
lastDigit := 0
|
|
||||||
hasComma := false
|
|
||||||
for _, r := range s {
|
|
||||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if r == ',' {
|
|
||||||
hasComma = true
|
|
||||||
}
|
|
||||||
lastDigit++
|
|
||||||
}
|
|
||||||
|
|
||||||
num := s[:lastDigit]
|
|
||||||
if hasComma {
|
|
||||||
num = strings.Replace(num, ",", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
val := &big.Rat{}
|
|
||||||
_, err := fmt.Sscanf(num, "%f", val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
|
||||||
if m, ok := bigBytesSizeTable[extra]; ok {
|
|
||||||
mv := (&big.Rat{}).SetInt(m)
|
|
||||||
val.Mul(val, mv)
|
|
||||||
rv := &big.Int{}
|
|
||||||
rv.Div(val.Num(), val.Denom())
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
|
||||||
}
|
|
||||||
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
@@ -1,143 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
)
|
|
||||||
|
|
||||||
// IEC Sizes.
|
|
||||||
// kibis of bits
|
|
||||||
const (
|
|
||||||
Byte = 1 << (iota * 10)
|
|
||||||
KiByte
|
|
||||||
MiByte
|
|
||||||
GiByte
|
|
||||||
TiByte
|
|
||||||
PiByte
|
|
||||||
EiByte
|
|
||||||
)
|
|
||||||
|
|
||||||
// SI Sizes.
|
|
||||||
const (
|
|
||||||
IByte = 1
|
|
||||||
KByte = IByte * 1000
|
|
||||||
MByte = KByte * 1000
|
|
||||||
GByte = MByte * 1000
|
|
||||||
TByte = GByte * 1000
|
|
||||||
PByte = TByte * 1000
|
|
||||||
EByte = PByte * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
var bytesSizeTable = map[string]uint64{
|
|
||||||
"b": Byte,
|
|
||||||
"kib": KiByte,
|
|
||||||
"kb": KByte,
|
|
||||||
"mib": MiByte,
|
|
||||||
"mb": MByte,
|
|
||||||
"gib": GiByte,
|
|
||||||
"gb": GByte,
|
|
||||||
"tib": TiByte,
|
|
||||||
"tb": TByte,
|
|
||||||
"pib": PiByte,
|
|
||||||
"pb": PByte,
|
|
||||||
"eib": EiByte,
|
|
||||||
"eb": EByte,
|
|
||||||
// Without suffix
|
|
||||||
"": Byte,
|
|
||||||
"ki": KiByte,
|
|
||||||
"k": KByte,
|
|
||||||
"mi": MiByte,
|
|
||||||
"m": MByte,
|
|
||||||
"gi": GiByte,
|
|
||||||
"g": GByte,
|
|
||||||
"ti": TiByte,
|
|
||||||
"t": TByte,
|
|
||||||
"pi": PiByte,
|
|
||||||
"p": PByte,
|
|
||||||
"ei": EiByte,
|
|
||||||
"e": EByte,
|
|
||||||
}
|
|
||||||
|
|
||||||
func logn(n, b float64) float64 {
|
|
||||||
return math.Log(n) / math.Log(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func humanateBytes(s uint64, base float64, sizes []string) string {
|
|
||||||
if s < 10 {
|
|
||||||
return fmt.Sprintf("%d B", s)
|
|
||||||
}
|
|
||||||
e := math.Floor(logn(float64(s), base))
|
|
||||||
suffix := sizes[int(e)]
|
|
||||||
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
|
||||||
f := "%.0f %s"
|
|
||||||
if val < 10 {
|
|
||||||
f = "%.1f %s"
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Sprintf(f, val, suffix)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bytes produces a human readable representation of an SI size.
|
|
||||||
//
|
|
||||||
// See also: ParseBytes.
|
|
||||||
//
|
|
||||||
// Bytes(82854982) -> 83 MB
|
|
||||||
func Bytes(s uint64) string {
|
|
||||||
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
|
||||||
return humanateBytes(s, 1000, sizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IBytes produces a human readable representation of an IEC size.
|
|
||||||
//
|
|
||||||
// See also: ParseBytes.
|
|
||||||
//
|
|
||||||
// IBytes(82854982) -> 79 MiB
|
|
||||||
func IBytes(s uint64) string {
|
|
||||||
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
|
||||||
return humanateBytes(s, 1024, sizes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseBytes parses a string representation of bytes into the number
|
|
||||||
// of bytes it represents.
|
|
||||||
//
|
|
||||||
// See Also: Bytes, IBytes.
|
|
||||||
//
|
|
||||||
// ParseBytes("42 MB") -> 42000000, nil
|
|
||||||
// ParseBytes("42 mib") -> 44040192, nil
|
|
||||||
func ParseBytes(s string) (uint64, error) {
|
|
||||||
lastDigit := 0
|
|
||||||
hasComma := false
|
|
||||||
for _, r := range s {
|
|
||||||
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if r == ',' {
|
|
||||||
hasComma = true
|
|
||||||
}
|
|
||||||
lastDigit++
|
|
||||||
}
|
|
||||||
|
|
||||||
num := s[:lastDigit]
|
|
||||||
if hasComma {
|
|
||||||
num = strings.Replace(num, ",", "", -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := strconv.ParseFloat(num, 64)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
|
||||||
if m, ok := bytesSizeTable[extra]; ok {
|
|
||||||
f *= float64(m)
|
|
||||||
if f >= math.MaxUint64 {
|
|
||||||
return 0, fmt.Errorf("too large: %v", s)
|
|
||||||
}
|
|
||||||
return uint64(f), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
|
||||||
}
|
|
||||||
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
@@ -1,116 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"math"
|
|
||||||
"math/big"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Comma produces a string form of the given number in base 10 with
|
|
||||||
// commas after every three orders of magnitude.
|
|
||||||
//
|
|
||||||
// e.g. Comma(834142) -> 834,142
|
|
||||||
func Comma(v int64) string {
|
|
||||||
sign := ""
|
|
||||||
|
|
||||||
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
|
||||||
if v == math.MinInt64 {
|
|
||||||
return "-9,223,372,036,854,775,808"
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 0 {
|
|
||||||
sign = "-"
|
|
||||||
v = 0 - v
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := []string{"", "", "", "", "", "", ""}
|
|
||||||
j := len(parts) - 1
|
|
||||||
|
|
||||||
for v > 999 {
|
|
||||||
parts[j] = strconv.FormatInt(v%1000, 10)
|
|
||||||
switch len(parts[j]) {
|
|
||||||
case 2:
|
|
||||||
parts[j] = "0" + parts[j]
|
|
||||||
case 1:
|
|
||||||
parts[j] = "00" + parts[j]
|
|
||||||
}
|
|
||||||
v = v / 1000
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
parts[j] = strconv.Itoa(int(v))
|
|
||||||
return sign + strings.Join(parts[j:], ",")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Commaf produces a string form of the given number in base 10 with
|
|
||||||
// commas after every three orders of magnitude.
|
|
||||||
//
|
|
||||||
// e.g. Commaf(834142.32) -> 834,142.32
|
|
||||||
func Commaf(v float64) string {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if v < 0 {
|
|
||||||
buf.Write([]byte{'-'})
|
|
||||||
v = 0 - v
|
|
||||||
}
|
|
||||||
|
|
||||||
comma := []byte{','}
|
|
||||||
|
|
||||||
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
|
||||||
pos := 0
|
|
||||||
if len(parts[0])%3 != 0 {
|
|
||||||
pos += len(parts[0]) % 3
|
|
||||||
buf.WriteString(parts[0][:pos])
|
|
||||||
buf.Write(comma)
|
|
||||||
}
|
|
||||||
for ; pos < len(parts[0]); pos += 3 {
|
|
||||||
buf.WriteString(parts[0][pos : pos+3])
|
|
||||||
buf.Write(comma)
|
|
||||||
}
|
|
||||||
buf.Truncate(buf.Len() - 1)
|
|
||||||
|
|
||||||
if len(parts) > 1 {
|
|
||||||
buf.Write([]byte{'.'})
|
|
||||||
buf.WriteString(parts[1])
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommafWithDigits works like the Commaf but limits the resulting
|
|
||||||
// string to the given number of decimal places.
|
|
||||||
//
|
|
||||||
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
|
||||||
func CommafWithDigits(f float64, decimals int) string {
|
|
||||||
return stripTrailingDigits(Commaf(f), decimals)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BigComma produces a string form of the given big.Int in base 10
|
|
||||||
// with commas after every three orders of magnitude.
|
|
||||||
func BigComma(b *big.Int) string {
|
|
||||||
sign := ""
|
|
||||||
if b.Sign() < 0 {
|
|
||||||
sign = "-"
|
|
||||||
b.Abs(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
athousand := big.NewInt(1000)
|
|
||||||
c := (&big.Int{}).Set(b)
|
|
||||||
_, m := oom(c, athousand)
|
|
||||||
parts := make([]string, m+1)
|
|
||||||
j := len(parts) - 1
|
|
||||||
|
|
||||||
mod := &big.Int{}
|
|
||||||
for b.Cmp(athousand) >= 0 {
|
|
||||||
b.DivMod(b, athousand, mod)
|
|
||||||
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
|
||||||
switch len(parts[j]) {
|
|
||||||
case 2:
|
|
||||||
parts[j] = "0" + parts[j]
|
|
||||||
case 1:
|
|
||||||
parts[j] = "00" + parts[j]
|
|
||||||
}
|
|
||||||
j--
|
|
||||||
}
|
|
||||||
parts[j] = strconv.Itoa(int(b.Int64()))
|
|
||||||
return sign + strings.Join(parts[j:], ",")
|
|
||||||
}
|
|
||||||
41
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
41
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
@@ -1,41 +0,0 @@
|
|||||||
//go:build go1.6
|
|
||||||
// +build go1.6
|
|
||||||
|
|
||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"math/big"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BigCommaf produces a string form of the given big.Float in base 10
|
|
||||||
// with commas after every three orders of magnitude.
|
|
||||||
func BigCommaf(v *big.Float) string {
|
|
||||||
buf := &bytes.Buffer{}
|
|
||||||
if v.Sign() < 0 {
|
|
||||||
buf.Write([]byte{'-'})
|
|
||||||
v.Abs(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
comma := []byte{','}
|
|
||||||
|
|
||||||
parts := strings.Split(v.Text('f', -1), ".")
|
|
||||||
pos := 0
|
|
||||||
if len(parts[0])%3 != 0 {
|
|
||||||
pos += len(parts[0]) % 3
|
|
||||||
buf.WriteString(parts[0][:pos])
|
|
||||||
buf.Write(comma)
|
|
||||||
}
|
|
||||||
for ; pos < len(parts[0]); pos += 3 {
|
|
||||||
buf.WriteString(parts[0][pos : pos+3])
|
|
||||||
buf.Write(comma)
|
|
||||||
}
|
|
||||||
buf.Truncate(buf.Len() - 1)
|
|
||||||
|
|
||||||
if len(parts) > 1 {
|
|
||||||
buf.Write([]byte{'.'})
|
|
||||||
buf.WriteString(parts[1])
|
|
||||||
}
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
49
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
49
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
@@ -1,49 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func stripTrailingZeros(s string) string {
|
|
||||||
if !strings.ContainsRune(s, '.') {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
offset := len(s) - 1
|
|
||||||
for offset > 0 {
|
|
||||||
if s[offset] == '.' {
|
|
||||||
offset--
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if s[offset] != '0' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
offset--
|
|
||||||
}
|
|
||||||
return s[:offset+1]
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripTrailingDigits(s string, digits int) string {
|
|
||||||
if i := strings.Index(s, "."); i >= 0 {
|
|
||||||
if digits <= 0 {
|
|
||||||
return s[:i]
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
if i+digits >= len(s) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[:i+digits]
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ftoa converts a float to a string with no trailing zeros.
|
|
||||||
func Ftoa(num float64) string {
|
|
||||||
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
|
||||||
}
|
|
||||||
|
|
||||||
// FtoaWithDigits converts a float to a string but limits the resulting string
|
|
||||||
// to the given number of decimal places, and no trailing zeros.
|
|
||||||
func FtoaWithDigits(num float64, digits int) string {
|
|
||||||
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
|
||||||
}
|
|
||||||
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
/*
|
|
||||||
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
|
||||||
|
|
||||||
Durations can be turned into strings such as "3 days ago", numbers
|
|
||||||
representing sizes like 82854982 into useful strings like, "83 MB" or
|
|
||||||
"79 MiB" (whichever you prefer).
|
|
||||||
*/
|
|
||||||
package humanize
|
|
||||||
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
@@ -1,192 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
/*
|
|
||||||
Slightly adapted from the source to fit go-humanize.
|
|
||||||
|
|
||||||
Author: https://github.com/gorhill
|
|
||||||
Source: https://gist.github.com/gorhill/5285193
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
renderFloatPrecisionMultipliers = [...]float64{
|
|
||||||
1,
|
|
||||||
10,
|
|
||||||
100,
|
|
||||||
1000,
|
|
||||||
10000,
|
|
||||||
100000,
|
|
||||||
1000000,
|
|
||||||
10000000,
|
|
||||||
100000000,
|
|
||||||
1000000000,
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFloatPrecisionRounders = [...]float64{
|
|
||||||
0.5,
|
|
||||||
0.05,
|
|
||||||
0.005,
|
|
||||||
0.0005,
|
|
||||||
0.00005,
|
|
||||||
0.000005,
|
|
||||||
0.0000005,
|
|
||||||
0.00000005,
|
|
||||||
0.000000005,
|
|
||||||
0.0000000005,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
|
||||||
// * thousands separator
|
|
||||||
// * decimal separator
|
|
||||||
// * decimal precision
|
|
||||||
//
|
|
||||||
// Usage: s := RenderFloat(format, n)
|
|
||||||
// The format parameter tells how to render the number n.
|
|
||||||
//
|
|
||||||
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
|
||||||
//
|
|
||||||
// Examples of format strings, given n = 12345.6789:
|
|
||||||
// "#,###.##" => "12,345.67"
|
|
||||||
// "#,###." => "12,345"
|
|
||||||
// "#,###" => "12345,678"
|
|
||||||
// "#\u202F###,##" => "12 345,68"
|
|
||||||
// "#.###,###### => 12.345,678900
|
|
||||||
// "" (aka default format) => 12,345.67
|
|
||||||
//
|
|
||||||
// The highest precision allowed is 9 digits after the decimal symbol.
|
|
||||||
// There is also a version for integer number, FormatInteger(),
|
|
||||||
// which is convenient for calls within template.
|
|
||||||
func FormatFloat(format string, n float64) string {
|
|
||||||
// Special cases:
|
|
||||||
// NaN = "NaN"
|
|
||||||
// +Inf = "+Infinity"
|
|
||||||
// -Inf = "-Infinity"
|
|
||||||
if math.IsNaN(n) {
|
|
||||||
return "NaN"
|
|
||||||
}
|
|
||||||
if n > math.MaxFloat64 {
|
|
||||||
return "Infinity"
|
|
||||||
}
|
|
||||||
if n < (0.0 - math.MaxFloat64) {
|
|
||||||
return "-Infinity"
|
|
||||||
}
|
|
||||||
|
|
||||||
// default format
|
|
||||||
precision := 2
|
|
||||||
decimalStr := "."
|
|
||||||
thousandStr := ","
|
|
||||||
positiveStr := ""
|
|
||||||
negativeStr := "-"
|
|
||||||
|
|
||||||
if len(format) > 0 {
|
|
||||||
format := []rune(format)
|
|
||||||
|
|
||||||
// If there is an explicit format directive,
|
|
||||||
// then default values are these:
|
|
||||||
precision = 9
|
|
||||||
thousandStr = ""
|
|
||||||
|
|
||||||
// collect indices of meaningful formatting directives
|
|
||||||
formatIndx := []int{}
|
|
||||||
for i, char := range format {
|
|
||||||
if char != '#' && char != '0' {
|
|
||||||
formatIndx = append(formatIndx, i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(formatIndx) > 0 {
|
|
||||||
// Directive at index 0:
|
|
||||||
// Must be a '+'
|
|
||||||
// Raise an error if not the case
|
|
||||||
// index: 0123456789
|
|
||||||
// +0.000,000
|
|
||||||
// +000,000.0
|
|
||||||
// +0000.00
|
|
||||||
// +0000
|
|
||||||
if formatIndx[0] == 0 {
|
|
||||||
if format[formatIndx[0]] != '+' {
|
|
||||||
panic("RenderFloat(): invalid positive sign directive")
|
|
||||||
}
|
|
||||||
positiveStr = "+"
|
|
||||||
formatIndx = formatIndx[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Two directives:
|
|
||||||
// First is thousands separator
|
|
||||||
// Raise an error if not followed by 3-digit
|
|
||||||
// 0123456789
|
|
||||||
// 0.000,000
|
|
||||||
// 000,000.00
|
|
||||||
if len(formatIndx) == 2 {
|
|
||||||
if (formatIndx[1] - formatIndx[0]) != 4 {
|
|
||||||
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
|
||||||
}
|
|
||||||
thousandStr = string(format[formatIndx[0]])
|
|
||||||
formatIndx = formatIndx[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// One directive:
|
|
||||||
// Directive is decimal separator
|
|
||||||
// The number of digit-specifier following the separator indicates wanted precision
|
|
||||||
// 0123456789
|
|
||||||
// 0.00
|
|
||||||
// 000,0000
|
|
||||||
if len(formatIndx) == 1 {
|
|
||||||
decimalStr = string(format[formatIndx[0]])
|
|
||||||
precision = len(format) - formatIndx[0] - 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate sign part
|
|
||||||
var signStr string
|
|
||||||
if n >= 0.000000001 {
|
|
||||||
signStr = positiveStr
|
|
||||||
} else if n <= -0.000000001 {
|
|
||||||
signStr = negativeStr
|
|
||||||
n = -n
|
|
||||||
} else {
|
|
||||||
signStr = ""
|
|
||||||
n = 0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
// split number into integer and fractional parts
|
|
||||||
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
|
||||||
|
|
||||||
// generate integer part string
|
|
||||||
intStr := strconv.FormatInt(int64(intf), 10)
|
|
||||||
|
|
||||||
// add thousand separator if required
|
|
||||||
if len(thousandStr) > 0 {
|
|
||||||
for i := len(intStr); i > 3; {
|
|
||||||
i -= 3
|
|
||||||
intStr = intStr[:i] + thousandStr + intStr[i:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no fractional part, we can leave now
|
|
||||||
if precision == 0 {
|
|
||||||
return signStr + intStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate fractional part
|
|
||||||
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
|
||||||
// may need padding
|
|
||||||
if len(fracStr) < precision {
|
|
||||||
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
|
||||||
}
|
|
||||||
|
|
||||||
return signStr + intStr + decimalStr + fracStr
|
|
||||||
}
|
|
||||||
|
|
||||||
// FormatInteger produces a formatted number as string.
|
|
||||||
// See FormatFloat.
|
|
||||||
func FormatInteger(format string, n int) string {
|
|
||||||
return FormatFloat(format, float64(n))
|
|
||||||
}
|
|
||||||
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
// Ordinal gives you the input number in a rank/ordinal format.
|
|
||||||
//
|
|
||||||
// Ordinal(3) -> 3rd
|
|
||||||
func Ordinal(x int) string {
|
|
||||||
suffix := "th"
|
|
||||||
switch x % 10 {
|
|
||||||
case 1:
|
|
||||||
if x%100 != 11 {
|
|
||||||
suffix = "st"
|
|
||||||
}
|
|
||||||
case 2:
|
|
||||||
if x%100 != 12 {
|
|
||||||
suffix = "nd"
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
if x%100 != 13 {
|
|
||||||
suffix = "rd"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strconv.Itoa(x) + suffix
|
|
||||||
}
|
|
||||||
127
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
127
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
@@ -1,127 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
var siPrefixTable = map[float64]string{
|
|
||||||
-30: "q", // quecto
|
|
||||||
-27: "r", // ronto
|
|
||||||
-24: "y", // yocto
|
|
||||||
-21: "z", // zepto
|
|
||||||
-18: "a", // atto
|
|
||||||
-15: "f", // femto
|
|
||||||
-12: "p", // pico
|
|
||||||
-9: "n", // nano
|
|
||||||
-6: "µ", // micro
|
|
||||||
-3: "m", // milli
|
|
||||||
0: "",
|
|
||||||
3: "k", // kilo
|
|
||||||
6: "M", // mega
|
|
||||||
9: "G", // giga
|
|
||||||
12: "T", // tera
|
|
||||||
15: "P", // peta
|
|
||||||
18: "E", // exa
|
|
||||||
21: "Z", // zetta
|
|
||||||
24: "Y", // yotta
|
|
||||||
27: "R", // ronna
|
|
||||||
30: "Q", // quetta
|
|
||||||
}
|
|
||||||
|
|
||||||
var revSIPrefixTable = revfmap(siPrefixTable)
|
|
||||||
|
|
||||||
// revfmap reverses the map and precomputes the power multiplier
|
|
||||||
func revfmap(in map[float64]string) map[string]float64 {
|
|
||||||
rv := map[string]float64{}
|
|
||||||
for k, v := range in {
|
|
||||||
rv[v] = math.Pow(10, k)
|
|
||||||
}
|
|
||||||
return rv
|
|
||||||
}
|
|
||||||
|
|
||||||
var riParseRegex *regexp.Regexp
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
ri := `^([\-0-9.]+)\s?([`
|
|
||||||
for _, v := range siPrefixTable {
|
|
||||||
ri += v
|
|
||||||
}
|
|
||||||
ri += `]?)(.*)`
|
|
||||||
|
|
||||||
riParseRegex = regexp.MustCompile(ri)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ComputeSI finds the most appropriate SI prefix for the given number
|
|
||||||
// and returns the prefix along with the value adjusted to be within
|
|
||||||
// that prefix.
|
|
||||||
//
|
|
||||||
// See also: SI, ParseSI.
|
|
||||||
//
|
|
||||||
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
|
||||||
func ComputeSI(input float64) (float64, string) {
|
|
||||||
if input == 0 {
|
|
||||||
return 0, ""
|
|
||||||
}
|
|
||||||
mag := math.Abs(input)
|
|
||||||
exponent := math.Floor(logn(mag, 10))
|
|
||||||
exponent = math.Floor(exponent/3) * 3
|
|
||||||
|
|
||||||
value := mag / math.Pow(10, exponent)
|
|
||||||
|
|
||||||
// Handle special case where value is exactly 1000.0
|
|
||||||
// Should return 1 M instead of 1000 k
|
|
||||||
if value == 1000.0 {
|
|
||||||
exponent += 3
|
|
||||||
value = mag / math.Pow(10, exponent)
|
|
||||||
}
|
|
||||||
|
|
||||||
value = math.Copysign(value, input)
|
|
||||||
|
|
||||||
prefix := siPrefixTable[exponent]
|
|
||||||
return value, prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
// SI returns a string with default formatting.
|
|
||||||
//
|
|
||||||
// SI uses Ftoa to format float value, removing trailing zeros.
|
|
||||||
//
|
|
||||||
// See also: ComputeSI, ParseSI.
|
|
||||||
//
|
|
||||||
// e.g. SI(1000000, "B") -> 1 MB
|
|
||||||
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
|
||||||
func SI(input float64, unit string) string {
|
|
||||||
value, prefix := ComputeSI(input)
|
|
||||||
return Ftoa(value) + " " + prefix + unit
|
|
||||||
}
|
|
||||||
|
|
||||||
// SIWithDigits works like SI but limits the resulting string to the
|
|
||||||
// given number of decimal places.
|
|
||||||
//
|
|
||||||
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
|
||||||
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
|
||||||
func SIWithDigits(input float64, decimals int, unit string) string {
|
|
||||||
value, prefix := ComputeSI(input)
|
|
||||||
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
|
||||||
}
|
|
||||||
|
|
||||||
var errInvalid = errors.New("invalid input")
|
|
||||||
|
|
||||||
// ParseSI parses an SI string back into the number and unit.
|
|
||||||
//
|
|
||||||
// See also: SI, ComputeSI.
|
|
||||||
//
|
|
||||||
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
|
||||||
func ParseSI(input string) (float64, string, error) {
|
|
||||||
found := riParseRegex.FindStringSubmatch(input)
|
|
||||||
if len(found) != 4 {
|
|
||||||
return 0, "", errInvalid
|
|
||||||
}
|
|
||||||
mag := revSIPrefixTable[found[2]]
|
|
||||||
unit := found[3]
|
|
||||||
|
|
||||||
base, err := strconv.ParseFloat(found[1], 64)
|
|
||||||
return base * mag, unit, err
|
|
||||||
}
|
|
||||||
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
@@ -1,117 +0,0 @@
|
|||||||
package humanize
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"sort"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Seconds-based time units
|
|
||||||
const (
|
|
||||||
Day = 24 * time.Hour
|
|
||||||
Week = 7 * Day
|
|
||||||
Month = 30 * Day
|
|
||||||
Year = 12 * Month
|
|
||||||
LongTime = 37 * Year
|
|
||||||
)
|
|
||||||
|
|
||||||
// Time formats a time into a relative string.
|
|
||||||
//
|
|
||||||
// Time(someT) -> "3 weeks ago"
|
|
||||||
func Time(then time.Time) string {
|
|
||||||
return RelTime(then, time.Now(), "ago", "from now")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A RelTimeMagnitude struct contains a relative time point at which
|
|
||||||
// the relative format of time will switch to a new format string. A
|
|
||||||
// slice of these in ascending order by their "D" field is passed to
|
|
||||||
// CustomRelTime to format durations.
|
|
||||||
//
|
|
||||||
// The Format field is a string that may contain a "%s" which will be
|
|
||||||
// replaced with the appropriate signed label (e.g. "ago" or "from
|
|
||||||
// now") and a "%d" that will be replaced by the quantity.
|
|
||||||
//
|
|
||||||
// The DivBy field is the amount of time the time difference must be
|
|
||||||
// divided by in order to display correctly.
|
|
||||||
//
|
|
||||||
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
|
||||||
// DivBy should be time.Minute so whatever the duration is will be
|
|
||||||
// expressed in minutes.
|
|
||||||
type RelTimeMagnitude struct {
|
|
||||||
D time.Duration
|
|
||||||
Format string
|
|
||||||
DivBy time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
var defaultMagnitudes = []RelTimeMagnitude{
|
|
||||||
{time.Second, "now", time.Second},
|
|
||||||
{2 * time.Second, "1 second %s", 1},
|
|
||||||
{time.Minute, "%d seconds %s", time.Second},
|
|
||||||
{2 * time.Minute, "1 minute %s", 1},
|
|
||||||
{time.Hour, "%d minutes %s", time.Minute},
|
|
||||||
{2 * time.Hour, "1 hour %s", 1},
|
|
||||||
{Day, "%d hours %s", time.Hour},
|
|
||||||
{2 * Day, "1 day %s", 1},
|
|
||||||
{Week, "%d days %s", Day},
|
|
||||||
{2 * Week, "1 week %s", 1},
|
|
||||||
{Month, "%d weeks %s", Week},
|
|
||||||
{2 * Month, "1 month %s", 1},
|
|
||||||
{Year, "%d months %s", Month},
|
|
||||||
{18 * Month, "1 year %s", 1},
|
|
||||||
{2 * Year, "2 years %s", 1},
|
|
||||||
{LongTime, "%d years %s", Year},
|
|
||||||
{math.MaxInt64, "a long while %s", 1},
|
|
||||||
}
|
|
||||||
|
|
||||||
// RelTime formats a time into a relative string.
|
|
||||||
//
|
|
||||||
// It takes two times and two labels. In addition to the generic time
|
|
||||||
// delta string (e.g. 5 minutes), the labels are used applied so that
|
|
||||||
// the label corresponding to the smaller time is applied.
|
|
||||||
//
|
|
||||||
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
|
||||||
func RelTime(a, b time.Time, albl, blbl string) string {
|
|
||||||
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomRelTime formats a time into a relative string.
|
|
||||||
//
|
|
||||||
// It takes two times two labels and a table of relative time formats.
|
|
||||||
// In addition to the generic time delta string (e.g. 5 minutes), the
|
|
||||||
// labels are used applied so that the label corresponding to the
|
|
||||||
// smaller time is applied.
|
|
||||||
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
|
||||||
lbl := albl
|
|
||||||
diff := b.Sub(a)
|
|
||||||
|
|
||||||
if a.After(b) {
|
|
||||||
lbl = blbl
|
|
||||||
diff = a.Sub(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
n := sort.Search(len(magnitudes), func(i int) bool {
|
|
||||||
return magnitudes[i].D > diff
|
|
||||||
})
|
|
||||||
|
|
||||||
if n >= len(magnitudes) {
|
|
||||||
n = len(magnitudes) - 1
|
|
||||||
}
|
|
||||||
mag := magnitudes[n]
|
|
||||||
args := []interface{}{}
|
|
||||||
escaped := false
|
|
||||||
for _, ch := range mag.Format {
|
|
||||||
if escaped {
|
|
||||||
switch ch {
|
|
||||||
case 's':
|
|
||||||
args = append(args, lbl)
|
|
||||||
case 'd':
|
|
||||||
args = append(args, diff/mag.DivBy)
|
|
||||||
}
|
|
||||||
escaped = false
|
|
||||||
} else {
|
|
||||||
escaped = ch == '%'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Sprintf(mag.Format, args...)
|
|
||||||
}
|
|
||||||
13
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
13
vendor/github.com/fsnotify/fsnotify/.cirrus.yml
generated
vendored
@@ -1,13 +0,0 @@
|
|||||||
freebsd_task:
|
|
||||||
name: 'FreeBSD'
|
|
||||||
freebsd_instance:
|
|
||||||
image_family: freebsd-13-2
|
|
||||||
install_script:
|
|
||||||
- pkg update -f
|
|
||||||
- pkg install -y go
|
|
||||||
test_script:
|
|
||||||
# run tests as user "cirrus" instead of root
|
|
||||||
- pw useradd cirrus -m
|
|
||||||
- chown -R cirrus:cirrus .
|
|
||||||
- FSNOTIFY_BUFFER=4096 sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
|
||||||
- sudo --preserve-env=FSNOTIFY_BUFFER -u cirrus go test -parallel 1 -race ./...
|
|
||||||
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
12
vendor/github.com/fsnotify/fsnotify/.editorconfig
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
root = true
|
|
||||||
|
|
||||||
[*.go]
|
|
||||||
indent_style = tab
|
|
||||||
indent_size = 4
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
[*.{yml,yaml}]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
insert_final_newline = true
|
|
||||||
trim_trailing_whitespace = true
|
|
||||||
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
1
vendor/github.com/fsnotify/fsnotify/.gitattributes
generated
vendored
@@ -1 +0,0 @@
|
|||||||
go.sum linguist-generated
|
|
||||||
7
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
7
vendor/github.com/fsnotify/fsnotify/.gitignore
generated
vendored
@@ -1,7 +0,0 @@
|
|||||||
# go test -c output
|
|
||||||
*.test
|
|
||||||
*.test.exe
|
|
||||||
|
|
||||||
# Output of go build ./cmd/fsnotify
|
|
||||||
/fsnotify
|
|
||||||
/fsnotify.exe
|
|
||||||
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
2
vendor/github.com/fsnotify/fsnotify/.mailmap
generated
vendored
@@ -1,2 +0,0 @@
|
|||||||
Chris Howey <howeyc@gmail.com> <chris@howey.me>
|
|
||||||
Nathan Youngman <git@nathany.com> <4566+nathany@users.noreply.github.com>
|
|
||||||
541
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
541
vendor/github.com/fsnotify/fsnotify/CHANGELOG.md
generated
vendored
@@ -1,541 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
Unreleased
|
|
||||||
----------
|
|
||||||
Nothing yet.
|
|
||||||
|
|
||||||
1.7.0 - 2023-10-22
|
|
||||||
------------------
|
|
||||||
This version of fsnotify needs Go 1.17.
|
|
||||||
|
|
||||||
### Additions
|
|
||||||
|
|
||||||
- illumos: add FEN backend to support illumos and Solaris. ([#371])
|
|
||||||
|
|
||||||
- all: add `NewBufferedWatcher()` to use a buffered channel, which can be useful
|
|
||||||
in cases where you can't control the kernel buffer and receive a large number
|
|
||||||
of events in bursts. ([#550], [#572])
|
|
||||||
|
|
||||||
- all: add `AddWith()`, which is identical to `Add()` but allows passing
|
|
||||||
options. ([#521])
|
|
||||||
|
|
||||||
- windows: allow setting the ReadDirectoryChangesW() buffer size with
|
|
||||||
`fsnotify.WithBufferSize()`; the default of 64K is the highest value that
|
|
||||||
works on all platforms and is enough for most purposes, but in some cases a
|
|
||||||
highest buffer is needed. ([#521])
|
|
||||||
|
|
||||||
### Changes and fixes
|
|
||||||
|
|
||||||
- inotify: remove watcher if a watched path is renamed ([#518])
|
|
||||||
|
|
||||||
After a rename the reported name wasn't updated, or even an empty string.
|
|
||||||
Inotify doesn't provide any good facilities to update it, so just remove the
|
|
||||||
watcher. This is already how it worked on kqueue and FEN.
|
|
||||||
|
|
||||||
On Windows this does work, and remains working.
|
|
||||||
|
|
||||||
- windows: don't listen for file attribute changes ([#520])
|
|
||||||
|
|
||||||
File attribute changes are sent as `FILE_ACTION_MODIFIED` by the Windows API,
|
|
||||||
with no way to see if they're a file write or attribute change, so would show
|
|
||||||
up as a fsnotify.Write event. This is never useful, and could result in many
|
|
||||||
spurious Write events.
|
|
||||||
|
|
||||||
- windows: return `ErrEventOverflow` if the buffer is full ([#525])
|
|
||||||
|
|
||||||
Before it would merely return "short read", making it hard to detect this
|
|
||||||
error.
|
|
||||||
|
|
||||||
- kqueue: make sure events for all files are delivered properly when removing a
|
|
||||||
watched directory ([#526])
|
|
||||||
|
|
||||||
Previously they would get sent with `""` (empty string) or `"."` as the path
|
|
||||||
name.
|
|
||||||
|
|
||||||
- kqueue: don't emit spurious Create events for symbolic links ([#524])
|
|
||||||
|
|
||||||
The link would get resolved but kqueue would "forget" it already saw the link
|
|
||||||
itself, resulting on a Create for every Write event for the directory.
|
|
||||||
|
|
||||||
- all: return `ErrClosed` on `Add()` when the watcher is closed ([#516])
|
|
||||||
|
|
||||||
- other: add `Watcher.Errors` and `Watcher.Events` to the no-op `Watcher` in
|
|
||||||
`backend_other.go`, making it easier to use on unsupported platforms such as
|
|
||||||
WASM, AIX, etc. ([#528])
|
|
||||||
|
|
||||||
- other: use the `backend_other.go` no-op if the `appengine` build tag is set;
|
|
||||||
Google AppEngine forbids usage of the unsafe package so the inotify backend
|
|
||||||
won't compile there.
|
|
||||||
|
|
||||||
[#371]: https://github.com/fsnotify/fsnotify/pull/371
|
|
||||||
[#516]: https://github.com/fsnotify/fsnotify/pull/516
|
|
||||||
[#518]: https://github.com/fsnotify/fsnotify/pull/518
|
|
||||||
[#520]: https://github.com/fsnotify/fsnotify/pull/520
|
|
||||||
[#521]: https://github.com/fsnotify/fsnotify/pull/521
|
|
||||||
[#524]: https://github.com/fsnotify/fsnotify/pull/524
|
|
||||||
[#525]: https://github.com/fsnotify/fsnotify/pull/525
|
|
||||||
[#526]: https://github.com/fsnotify/fsnotify/pull/526
|
|
||||||
[#528]: https://github.com/fsnotify/fsnotify/pull/528
|
|
||||||
[#537]: https://github.com/fsnotify/fsnotify/pull/537
|
|
||||||
[#550]: https://github.com/fsnotify/fsnotify/pull/550
|
|
||||||
[#572]: https://github.com/fsnotify/fsnotify/pull/572
|
|
||||||
|
|
||||||
1.6.0 - 2022-10-13
|
|
||||||
------------------
|
|
||||||
This version of fsnotify needs Go 1.16 (this was already the case since 1.5.1,
|
|
||||||
but not documented). It also increases the minimum Linux version to 2.6.32.
|
|
||||||
|
|
||||||
### Additions
|
|
||||||
|
|
||||||
- all: add `Event.Has()` and `Op.Has()` ([#477])
|
|
||||||
|
|
||||||
This makes checking events a lot easier; for example:
|
|
||||||
|
|
||||||
if event.Op&Write == Write && !(event.Op&Remove == Remove) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Becomes:
|
|
||||||
|
|
||||||
if event.Has(Write) && !event.Has(Remove) {
|
|
||||||
}
|
|
||||||
|
|
||||||
- all: add cmd/fsnotify ([#463])
|
|
||||||
|
|
||||||
A command-line utility for testing and some examples.
|
|
||||||
|
|
||||||
### Changes and fixes
|
|
||||||
|
|
||||||
- inotify: don't ignore events for files that don't exist ([#260], [#470])
|
|
||||||
|
|
||||||
Previously the inotify watcher would call `os.Lstat()` to check if a file
|
|
||||||
still exists before emitting events.
|
|
||||||
|
|
||||||
This was inconsistent with other platforms and resulted in inconsistent event
|
|
||||||
reporting (e.g. when a file is quickly removed and re-created), and generally
|
|
||||||
a source of confusion. It was added in 2013 to fix a memory leak that no
|
|
||||||
longer exists.
|
|
||||||
|
|
||||||
- all: return `ErrNonExistentWatch` when `Remove()` is called on a path that's
|
|
||||||
not watched ([#460])
|
|
||||||
|
|
||||||
- inotify: replace epoll() with non-blocking inotify ([#434])
|
|
||||||
|
|
||||||
Non-blocking inotify was not generally available at the time this library was
|
|
||||||
written in 2014, but now it is. As a result, the minimum Linux version is
|
|
||||||
bumped from 2.6.27 to 2.6.32. This hugely simplifies the code and is faster.
|
|
||||||
|
|
||||||
- kqueue: don't check for events every 100ms ([#480])
|
|
||||||
|
|
||||||
The watcher would wake up every 100ms, even when there was nothing to do. Now
|
|
||||||
it waits until there is something to do.
|
|
||||||
|
|
||||||
- macos: retry opening files on EINTR ([#475])
|
|
||||||
|
|
||||||
- kqueue: skip unreadable files ([#479])
|
|
||||||
|
|
||||||
kqueue requires a file descriptor for every file in a directory; this would
|
|
||||||
fail if a file was unreadable by the current user. Now these files are simply
|
|
||||||
skipped.
|
|
||||||
|
|
||||||
- windows: fix renaming a watched directory if the parent is also watched ([#370])
|
|
||||||
|
|
||||||
- windows: increase buffer size from 4K to 64K ([#485])
|
|
||||||
|
|
||||||
- windows: close file handle on Remove() ([#288])
|
|
||||||
|
|
||||||
- kqueue: put pathname in the error if watching a file fails ([#471])
|
|
||||||
|
|
||||||
- inotify, windows: calling Close() more than once could race ([#465])
|
|
||||||
|
|
||||||
- kqueue: improve Close() performance ([#233])
|
|
||||||
|
|
||||||
- all: various documentation additions and clarifications.
|
|
||||||
|
|
||||||
[#233]: https://github.com/fsnotify/fsnotify/pull/233
|
|
||||||
[#260]: https://github.com/fsnotify/fsnotify/pull/260
|
|
||||||
[#288]: https://github.com/fsnotify/fsnotify/pull/288
|
|
||||||
[#370]: https://github.com/fsnotify/fsnotify/pull/370
|
|
||||||
[#434]: https://github.com/fsnotify/fsnotify/pull/434
|
|
||||||
[#460]: https://github.com/fsnotify/fsnotify/pull/460
|
|
||||||
[#463]: https://github.com/fsnotify/fsnotify/pull/463
|
|
||||||
[#465]: https://github.com/fsnotify/fsnotify/pull/465
|
|
||||||
[#470]: https://github.com/fsnotify/fsnotify/pull/470
|
|
||||||
[#471]: https://github.com/fsnotify/fsnotify/pull/471
|
|
||||||
[#475]: https://github.com/fsnotify/fsnotify/pull/475
|
|
||||||
[#477]: https://github.com/fsnotify/fsnotify/pull/477
|
|
||||||
[#479]: https://github.com/fsnotify/fsnotify/pull/479
|
|
||||||
[#480]: https://github.com/fsnotify/fsnotify/pull/480
|
|
||||||
[#485]: https://github.com/fsnotify/fsnotify/pull/485
|
|
||||||
|
|
||||||
## [1.5.4] - 2022-04-25
|
|
||||||
|
|
||||||
* Windows: add missing defer to `Watcher.WatchList` [#447](https://github.com/fsnotify/fsnotify/pull/447)
|
|
||||||
* go.mod: use latest x/sys [#444](https://github.com/fsnotify/fsnotify/pull/444)
|
|
||||||
* Fix compilation for OpenBSD [#443](https://github.com/fsnotify/fsnotify/pull/443)
|
|
||||||
|
|
||||||
## [1.5.3] - 2022-04-22
|
|
||||||
|
|
||||||
* This version is retracted. An incorrect branch is published accidentally [#445](https://github.com/fsnotify/fsnotify/issues/445)
|
|
||||||
|
|
||||||
## [1.5.2] - 2022-04-21
|
|
||||||
|
|
||||||
* Add a feature to return the directories and files that are being monitored [#374](https://github.com/fsnotify/fsnotify/pull/374)
|
|
||||||
* Fix potential crash on windows if `raw.FileNameLength` exceeds `syscall.MAX_PATH` [#361](https://github.com/fsnotify/fsnotify/pull/361)
|
|
||||||
* Allow build on unsupported GOOS [#424](https://github.com/fsnotify/fsnotify/pull/424)
|
|
||||||
* Don't set `poller.fd` twice in `newFdPoller` [#406](https://github.com/fsnotify/fsnotify/pull/406)
|
|
||||||
* fix go vet warnings: call to `(*T).Fatalf` from a non-test goroutine [#416](https://github.com/fsnotify/fsnotify/pull/416)
|
|
||||||
|
|
||||||
## [1.5.1] - 2021-08-24
|
|
||||||
|
|
||||||
* Revert Add AddRaw to not follow symlinks [#394](https://github.com/fsnotify/fsnotify/pull/394)
|
|
||||||
|
|
||||||
## [1.5.0] - 2021-08-20
|
|
||||||
|
|
||||||
* Go: Increase minimum required version to Go 1.12 [#381](https://github.com/fsnotify/fsnotify/pull/381)
|
|
||||||
* Feature: Add AddRaw method which does not follow symlinks when adding a watch [#289](https://github.com/fsnotify/fsnotify/pull/298)
|
|
||||||
* Windows: Follow symlinks by default like on all other systems [#289](https://github.com/fsnotify/fsnotify/pull/289)
|
|
||||||
* CI: Use GitHub Actions for CI and cover go 1.12-1.17
|
|
||||||
[#378](https://github.com/fsnotify/fsnotify/pull/378)
|
|
||||||
[#381](https://github.com/fsnotify/fsnotify/pull/381)
|
|
||||||
[#385](https://github.com/fsnotify/fsnotify/pull/385)
|
|
||||||
* Go 1.14+: Fix unsafe pointer conversion [#325](https://github.com/fsnotify/fsnotify/pull/325)
|
|
||||||
|
|
||||||
## [1.4.9] - 2020-03-11
|
|
||||||
|
|
||||||
* Move example usage to the readme #329. This may resolve #328.
|
|
||||||
|
|
||||||
## [1.4.8] - 2020-03-10
|
|
||||||
|
|
||||||
* CI: test more go versions (@nathany 1d13583d846ea9d66dcabbfefbfb9d8e6fb05216)
|
|
||||||
* Tests: Queued inotify events could have been read by the test before max_queued_events was hit (@matthias-stone #265)
|
|
||||||
* Tests: t.Fatalf -> t.Errorf in go routines (@gdey #266)
|
|
||||||
* CI: Less verbosity (@nathany #267)
|
|
||||||
* Tests: Darwin: Exchangedata is deprecated on 10.13 (@nathany #267)
|
|
||||||
* Tests: Check if channels are closed in the example (@alexeykazakov #244)
|
|
||||||
* CI: Only run golint on latest version of go and fix issues (@cpuguy83 #284)
|
|
||||||
* CI: Add windows to travis matrix (@cpuguy83 #284)
|
|
||||||
* Docs: Remover appveyor badge (@nathany 11844c0959f6fff69ba325d097fce35bd85a8e93)
|
|
||||||
* Linux: create epoll and pipe fds with close-on-exec (@JohannesEbke #219)
|
|
||||||
* Linux: open files with close-on-exec (@linxiulei #273)
|
|
||||||
* Docs: Plan to support fanotify (@nathany ab058b44498e8b7566a799372a39d150d9ea0119 )
|
|
||||||
* Project: Add go.mod (@nathany #309)
|
|
||||||
* Project: Revise editor config (@nathany #309)
|
|
||||||
* Project: Update copyright for 2019 (@nathany #309)
|
|
||||||
* CI: Drop go1.8 from CI matrix (@nathany #309)
|
|
||||||
* Docs: Updating the FAQ section for supportability with NFS & FUSE filesystems (@Pratik32 4bf2d1fec78374803a39307bfb8d340688f4f28e )
|
|
||||||
|
|
||||||
## [1.4.7] - 2018-01-09
|
|
||||||
|
|
||||||
* BSD/macOS: Fix possible deadlock on closing the watcher on kqueue (thanks @nhooyr and @glycerine)
|
|
||||||
* Tests: Fix missing verb on format string (thanks @rchiossi)
|
|
||||||
* Linux: Fix deadlock in Remove (thanks @aarondl)
|
|
||||||
* Linux: Watch.Add improvements (avoid race, fix consistency, reduce garbage) (thanks @twpayne)
|
|
||||||
* Docs: Moved FAQ into the README (thanks @vahe)
|
|
||||||
* Linux: Properly handle inotify's IN_Q_OVERFLOW event (thanks @zeldovich)
|
|
||||||
* Docs: replace references to OS X with macOS
|
|
||||||
|
|
||||||
## [1.4.2] - 2016-10-10
|
|
||||||
|
|
||||||
* Linux: use InotifyInit1 with IN_CLOEXEC to stop leaking a file descriptor to a child process when using fork/exec [#178](https://github.com/fsnotify/fsnotify/pull/178) (thanks @pattyshack)
|
|
||||||
|
|
||||||
## [1.4.1] - 2016-10-04
|
|
||||||
|
|
||||||
* Fix flaky inotify stress test on Linux [#177](https://github.com/fsnotify/fsnotify/pull/177) (thanks @pattyshack)
|
|
||||||
|
|
||||||
## [1.4.0] - 2016-10-01
|
|
||||||
|
|
||||||
* add a String() method to Event.Op [#165](https://github.com/fsnotify/fsnotify/pull/165) (thanks @oozie)
|
|
||||||
|
|
||||||
## [1.3.1] - 2016-06-28
|
|
||||||
|
|
||||||
* Windows: fix for double backslash when watching the root of a drive [#151](https://github.com/fsnotify/fsnotify/issues/151) (thanks @brunoqc)
|
|
||||||
|
|
||||||
## [1.3.0] - 2016-04-19
|
|
||||||
|
|
||||||
* Support linux/arm64 by [patching](https://go-review.googlesource.com/#/c/21971/) x/sys/unix and switching to to it from syscall (thanks @suihkulokki) [#135](https://github.com/fsnotify/fsnotify/pull/135)
|
|
||||||
|
|
||||||
## [1.2.10] - 2016-03-02
|
|
||||||
|
|
||||||
* Fix golint errors in windows.go [#121](https://github.com/fsnotify/fsnotify/pull/121) (thanks @tiffanyfj)
|
|
||||||
|
|
||||||
## [1.2.9] - 2016-01-13
|
|
||||||
|
|
||||||
kqueue: Fix logic for CREATE after REMOVE [#111](https://github.com/fsnotify/fsnotify/pull/111) (thanks @bep)
|
|
||||||
|
|
||||||
## [1.2.8] - 2015-12-17
|
|
||||||
|
|
||||||
* kqueue: fix race condition in Close [#105](https://github.com/fsnotify/fsnotify/pull/105) (thanks @djui for reporting the issue and @ppknap for writing a failing test)
|
|
||||||
* inotify: fix race in test
|
|
||||||
* enable race detection for continuous integration (Linux, Mac, Windows)
|
|
||||||
|
|
||||||
## [1.2.5] - 2015-10-17
|
|
||||||
|
|
||||||
* inotify: use epoll_create1 for arm64 support (requires Linux 2.6.27 or later) [#100](https://github.com/fsnotify/fsnotify/pull/100) (thanks @suihkulokki)
|
|
||||||
* inotify: fix path leaks [#73](https://github.com/fsnotify/fsnotify/pull/73) (thanks @chamaken)
|
|
||||||
* kqueue: watch for rename events on subdirectories [#83](https://github.com/fsnotify/fsnotify/pull/83) (thanks @guotie)
|
|
||||||
* kqueue: avoid infinite loops from symlinks cycles [#101](https://github.com/fsnotify/fsnotify/pull/101) (thanks @illicitonion)
|
|
||||||
|
|
||||||
## [1.2.1] - 2015-10-14
|
|
||||||
|
|
||||||
* kqueue: don't watch named pipes [#98](https://github.com/fsnotify/fsnotify/pull/98) (thanks @evanphx)
|
|
||||||
|
|
||||||
## [1.2.0] - 2015-02-08
|
|
||||||
|
|
||||||
* inotify: use epoll to wake up readEvents [#66](https://github.com/fsnotify/fsnotify/pull/66) (thanks @PieterD)
|
|
||||||
* inotify: closing watcher should now always shut down goroutine [#63](https://github.com/fsnotify/fsnotify/pull/63) (thanks @PieterD)
|
|
||||||
* kqueue: close kqueue after removing watches, fixes [#59](https://github.com/fsnotify/fsnotify/issues/59)
|
|
||||||
|
|
||||||
## [1.1.1] - 2015-02-05
|
|
||||||
|
|
||||||
* inotify: Retry read on EINTR [#61](https://github.com/fsnotify/fsnotify/issues/61) (thanks @PieterD)
|
|
||||||
|
|
||||||
## [1.1.0] - 2014-12-12
|
|
||||||
|
|
||||||
* kqueue: rework internals [#43](https://github.com/fsnotify/fsnotify/pull/43)
|
|
||||||
* add low-level functions
|
|
||||||
* only need to store flags on directories
|
|
||||||
* less mutexes [#13](https://github.com/fsnotify/fsnotify/issues/13)
|
|
||||||
* done can be an unbuffered channel
|
|
||||||
* remove calls to os.NewSyscallError
|
|
||||||
* More efficient string concatenation for Event.String() [#52](https://github.com/fsnotify/fsnotify/pull/52) (thanks @mdlayher)
|
|
||||||
* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/fsnotify/fsnotify/issues/48)
|
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
|
||||||
|
|
||||||
## [1.0.4] - 2014-09-07
|
|
||||||
|
|
||||||
* kqueue: add dragonfly to the build tags.
|
|
||||||
* Rename source code files, rearrange code so exported APIs are at the top.
|
|
||||||
* Add done channel to example code. [#37](https://github.com/fsnotify/fsnotify/pull/37) (thanks @chenyukang)
|
|
||||||
|
|
||||||
## [1.0.3] - 2014-08-19
|
|
||||||
|
|
||||||
* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/fsnotify/fsnotify/issues/36)
|
|
||||||
|
|
||||||
## [1.0.2] - 2014-08-17
|
|
||||||
|
|
||||||
* [Fix] Missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
|
||||||
* [Fix] Make ./path and path equivalent. (thanks @zhsso)
|
|
||||||
|
|
||||||
## [1.0.0] - 2014-08-15
|
|
||||||
|
|
||||||
* [API] Remove AddWatch on Windows, use Add.
|
|
||||||
* Improve documentation for exported identifiers. [#30](https://github.com/fsnotify/fsnotify/issues/30)
|
|
||||||
* Minor updates based on feedback from golint.
|
|
||||||
|
|
||||||
## dev / 2014-07-09
|
|
||||||
|
|
||||||
* Moved to [github.com/fsnotify/fsnotify](https://github.com/fsnotify/fsnotify).
|
|
||||||
* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
|
|
||||||
|
|
||||||
## dev / 2014-07-04
|
|
||||||
|
|
||||||
* kqueue: fix incorrect mutex used in Close()
|
|
||||||
* Update example to demonstrate usage of Op.
|
|
||||||
|
|
||||||
## dev / 2014-06-28
|
|
||||||
|
|
||||||
* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/fsnotify/fsnotify/issues/4)
|
|
||||||
* Fix for String() method on Event (thanks Alex Brainman)
|
|
||||||
* Don't build on Plan 9 or Solaris (thanks @4ad)
|
|
||||||
|
|
||||||
## dev / 2014-06-21
|
|
||||||
|
|
||||||
* Events channel of type Event rather than *Event.
|
|
||||||
* [internal] use syscall constants directly for inotify and kqueue.
|
|
||||||
* [internal] kqueue: rename events to kevents and fileEvent to event.
|
|
||||||
|
|
||||||
## dev / 2014-06-19
|
|
||||||
|
|
||||||
* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
|
|
||||||
* [internal] remove cookie from Event struct (unused).
|
|
||||||
* [internal] Event struct has the same definition across every OS.
|
|
||||||
* [internal] remove internal watch and removeWatch methods.
|
|
||||||
|
|
||||||
## dev / 2014-06-12
|
|
||||||
|
|
||||||
* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
|
|
||||||
* [API] Pluralized channel names: Events and Errors.
|
|
||||||
* [API] Renamed FileEvent struct to Event.
|
|
||||||
* [API] Op constants replace methods like IsCreate().
|
|
||||||
|
|
||||||
## dev / 2014-06-12
|
|
||||||
|
|
||||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
|
||||||
|
|
||||||
## dev / 2014-05-23
|
|
||||||
|
|
||||||
* [API] Remove current implementation of WatchFlags.
|
|
||||||
* current implementation doesn't take advantage of OS for efficiency
|
|
||||||
* provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
|
|
||||||
* no tests for the current implementation
|
|
||||||
* not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
|
|
||||||
|
|
||||||
## [0.9.3] - 2014-12-31
|
|
||||||
|
|
||||||
* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/fsnotify/fsnotify/issues/51)
|
|
||||||
|
|
||||||
## [0.9.2] - 2014-08-17
|
|
||||||
|
|
||||||
* [Backport] Fix missing create events on macOS. [#14](https://github.com/fsnotify/fsnotify/issues/14) (thanks @zhsso)
|
|
||||||
|
|
||||||
## [0.9.1] - 2014-06-12
|
|
||||||
|
|
||||||
* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
|
|
||||||
|
|
||||||
## [0.9.0] - 2014-01-17
|
|
||||||
|
|
||||||
* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
|
|
||||||
* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
|
|
||||||
* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
|
|
||||||
|
|
||||||
## [0.8.12] - 2013-11-13
|
|
||||||
|
|
||||||
* [API] Remove FD_SET and friends from Linux adapter
|
|
||||||
|
|
||||||
## [0.8.11] - 2013-11-02
|
|
||||||
|
|
||||||
* [Doc] Add Changelog [#72][] (thanks @nathany)
|
|
||||||
* [Doc] Spotlight and double modify events on macOS [#62][] (reported by @paulhammond)
|
|
||||||
|
|
||||||
## [0.8.10] - 2013-10-19
|
|
||||||
|
|
||||||
* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
|
|
||||||
* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
|
|
||||||
* [Doc] specify OS-specific limits in README (thanks @debrando)
|
|
||||||
|
|
||||||
## [0.8.9] - 2013-09-08
|
|
||||||
|
|
||||||
* [Doc] Contributing (thanks @nathany)
|
|
||||||
* [Doc] update package path in example code [#63][] (thanks @paulhammond)
|
|
||||||
* [Doc] GoCI badge in README (Linux only) [#60][]
|
|
||||||
* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
|
|
||||||
|
|
||||||
## [0.8.8] - 2013-06-17
|
|
||||||
|
|
||||||
* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
|
|
||||||
|
|
||||||
## [0.8.7] - 2013-06-03
|
|
||||||
|
|
||||||
* [API] Make syscall flags internal
|
|
||||||
* [Fix] inotify: ignore event changes
|
|
||||||
* [Fix] race in symlink test [#45][] (reported by @srid)
|
|
||||||
* [Fix] tests on Windows
|
|
||||||
* lower case error messages
|
|
||||||
|
|
||||||
## [0.8.6] - 2013-05-23
|
|
||||||
|
|
||||||
* kqueue: Use EVT_ONLY flag on Darwin
|
|
||||||
* [Doc] Update README with full example
|
|
||||||
|
|
||||||
## [0.8.5] - 2013-05-09
|
|
||||||
|
|
||||||
* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
|
|
||||||
|
|
||||||
## [0.8.4] - 2013-04-07
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
|
|
||||||
|
|
||||||
## [0.8.3] - 2013-03-13
|
|
||||||
|
|
||||||
* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
|
|
||||||
* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
|
|
||||||
|
|
||||||
## [0.8.2] - 2013-02-07
|
|
||||||
|
|
||||||
* [Doc] add Authors
|
|
||||||
* [Fix] fix data races for map access [#29][] (thanks @fsouza)
|
|
||||||
|
|
||||||
## [0.8.1] - 2013-01-09
|
|
||||||
|
|
||||||
* [Fix] Windows path separators
|
|
||||||
* [Doc] BSD License
|
|
||||||
|
|
||||||
## [0.8.0] - 2012-11-09
|
|
||||||
|
|
||||||
* kqueue: directory watching improvements (thanks @vmirage)
|
|
||||||
* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
|
|
||||||
* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
|
|
||||||
|
|
||||||
## [0.7.4] - 2012-10-09
|
|
||||||
|
|
||||||
* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
|
|
||||||
* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
|
|
||||||
* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
|
|
||||||
* [Fix] kqueue: modify after recreation of file
|
|
||||||
|
|
||||||
## [0.7.3] - 2012-09-27
|
|
||||||
|
|
||||||
* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
|
|
||||||
* [Fix] kqueue: no longer get duplicate CREATE events
|
|
||||||
|
|
||||||
## [0.7.2] - 2012-09-01
|
|
||||||
|
|
||||||
* kqueue: events for created directories
|
|
||||||
|
|
||||||
## [0.7.1] - 2012-07-14
|
|
||||||
|
|
||||||
* [Fix] for renaming files
|
|
||||||
|
|
||||||
## [0.7.0] - 2012-07-02
|
|
||||||
|
|
||||||
* [Feature] FSNotify flags
|
|
||||||
* [Fix] inotify: Added file name back to event path
|
|
||||||
|
|
||||||
## [0.6.0] - 2012-06-06
|
|
||||||
|
|
||||||
* kqueue: watch files after directory created (thanks @tmc)
|
|
||||||
|
|
||||||
## [0.5.1] - 2012-05-22
|
|
||||||
|
|
||||||
* [Fix] inotify: remove all watches before Close()
|
|
||||||
|
|
||||||
## [0.5.0] - 2012-05-03
|
|
||||||
|
|
||||||
* [API] kqueue: return errors during watch instead of sending over channel
|
|
||||||
* kqueue: match symlink behavior on Linux
|
|
||||||
* inotify: add `DELETE_SELF` (requested by @taralx)
|
|
||||||
* [Fix] kqueue: handle EINTR (reported by @robfig)
|
|
||||||
* [Doc] Godoc example [#1][] (thanks @davecheney)
|
|
||||||
|
|
||||||
## [0.4.0] - 2012-03-30
|
|
||||||
|
|
||||||
* Go 1 released: build with go tool
|
|
||||||
* [Feature] Windows support using winfsnotify
|
|
||||||
* Windows does not have attribute change notifications
|
|
||||||
* Roll attribute notifications into IsModify
|
|
||||||
|
|
||||||
## [0.3.0] - 2012-02-19
|
|
||||||
|
|
||||||
* kqueue: add files when watch directory
|
|
||||||
|
|
||||||
## [0.2.0] - 2011-12-30
|
|
||||||
|
|
||||||
* update to latest Go weekly code
|
|
||||||
|
|
||||||
## [0.1.0] - 2011-10-19
|
|
||||||
|
|
||||||
* kqueue: add watch on file creation to match inotify
|
|
||||||
* kqueue: create file event
|
|
||||||
* inotify: ignore `IN_IGNORED` events
|
|
||||||
* event String()
|
|
||||||
* linux: common FileEvent functions
|
|
||||||
* initial commit
|
|
||||||
|
|
||||||
[#79]: https://github.com/howeyc/fsnotify/pull/79
|
|
||||||
[#77]: https://github.com/howeyc/fsnotify/pull/77
|
|
||||||
[#72]: https://github.com/howeyc/fsnotify/issues/72
|
|
||||||
[#71]: https://github.com/howeyc/fsnotify/issues/71
|
|
||||||
[#70]: https://github.com/howeyc/fsnotify/issues/70
|
|
||||||
[#63]: https://github.com/howeyc/fsnotify/issues/63
|
|
||||||
[#62]: https://github.com/howeyc/fsnotify/issues/62
|
|
||||||
[#60]: https://github.com/howeyc/fsnotify/issues/60
|
|
||||||
[#59]: https://github.com/howeyc/fsnotify/issues/59
|
|
||||||
[#49]: https://github.com/howeyc/fsnotify/issues/49
|
|
||||||
[#45]: https://github.com/howeyc/fsnotify/issues/45
|
|
||||||
[#40]: https://github.com/howeyc/fsnotify/issues/40
|
|
||||||
[#36]: https://github.com/howeyc/fsnotify/issues/36
|
|
||||||
[#33]: https://github.com/howeyc/fsnotify/issues/33
|
|
||||||
[#29]: https://github.com/howeyc/fsnotify/issues/29
|
|
||||||
[#25]: https://github.com/howeyc/fsnotify/issues/25
|
|
||||||
[#24]: https://github.com/howeyc/fsnotify/issues/24
|
|
||||||
[#21]: https://github.com/howeyc/fsnotify/issues/21
|
|
||||||
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
26
vendor/github.com/fsnotify/fsnotify/CONTRIBUTING.md
generated
vendored
@@ -1,26 +0,0 @@
|
|||||||
Thank you for your interest in contributing to fsnotify! We try to review and
|
|
||||||
merge PRs in a reasonable timeframe, but please be aware that:
|
|
||||||
|
|
||||||
- To avoid "wasted" work, please discus changes on the issue tracker first. You
|
|
||||||
can just send PRs, but they may end up being rejected for one reason or the
|
|
||||||
other.
|
|
||||||
|
|
||||||
- fsnotify is a cross-platform library, and changes must work reasonably well on
|
|
||||||
all supported platforms.
|
|
||||||
|
|
||||||
- Changes will need to be compatible; old code should still compile, and the
|
|
||||||
runtime behaviour can't change in ways that are likely to lead to problems for
|
|
||||||
users.
|
|
||||||
|
|
||||||
Testing
|
|
||||||
-------
|
|
||||||
Just `go test ./...` runs all the tests; the CI runs this on all supported
|
|
||||||
platforms. Testing different platforms locally can be done with something like
|
|
||||||
[goon] or [Vagrant], but this isn't super-easy to set up at the moment.
|
|
||||||
|
|
||||||
Use the `-short` flag to make the "stress test" run faster.
|
|
||||||
|
|
||||||
|
|
||||||
[goon]: https://github.com/arp242/goon
|
|
||||||
[Vagrant]: https://www.vagrantup.com/
|
|
||||||
[integration_test.go]: /integration_test.go
|
|
||||||
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
25
vendor/github.com/fsnotify/fsnotify/LICENSE
generated
vendored
@@ -1,25 +0,0 @@
|
|||||||
Copyright © 2012 The Go Authors. All rights reserved.
|
|
||||||
Copyright © fsnotify Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without modification,
|
|
||||||
are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer in the documentation and/or
|
|
||||||
other materials provided with the distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its contributors may be used
|
|
||||||
to endorse or promote products derived from this software without specific
|
|
||||||
prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
184
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
184
vendor/github.com/fsnotify/fsnotify/README.md
generated
vendored
@@ -1,184 +0,0 @@
|
|||||||
fsnotify is a Go library to provide cross-platform filesystem notifications on
|
|
||||||
Windows, Linux, macOS, BSD, and illumos.
|
|
||||||
|
|
||||||
Go 1.17 or newer is required; the full documentation is at
|
|
||||||
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
Platform support:
|
|
||||||
|
|
||||||
| Backend | OS | Status |
|
|
||||||
| :-------------------- | :--------- | :------------------------------------------------------------------------ |
|
|
||||||
| inotify | Linux | Supported |
|
|
||||||
| kqueue | BSD, macOS | Supported |
|
|
||||||
| ReadDirectoryChangesW | Windows | Supported |
|
|
||||||
| FEN | illumos | Supported |
|
|
||||||
| fanotify | Linux 5.9+ | [Not yet](https://github.com/fsnotify/fsnotify/issues/114) |
|
|
||||||
| AHAFS | AIX | [aix branch]; experimental due to lack of maintainer and test environment |
|
|
||||||
| FSEvents | macOS | [Needs support in x/sys/unix][fsevents] |
|
|
||||||
| USN Journals | Windows | [Needs support in x/sys/windows][usn] |
|
|
||||||
| Polling | *All* | [Not yet](https://github.com/fsnotify/fsnotify/issues/9) |
|
|
||||||
|
|
||||||
Linux and illumos should include Android and Solaris, but these are currently
|
|
||||||
untested.
|
|
||||||
|
|
||||||
[fsevents]: https://github.com/fsnotify/fsnotify/issues/11#issuecomment-1279133120
|
|
||||||
[usn]: https://github.com/fsnotify/fsnotify/issues/53#issuecomment-1279829847
|
|
||||||
[aix branch]: https://github.com/fsnotify/fsnotify/issues/353#issuecomment-1284590129
|
|
||||||
|
|
||||||
Usage
|
|
||||||
-----
|
|
||||||
A basic example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/fsnotify/fsnotify"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// Create new watcher.
|
|
||||||
watcher, err := fsnotify.NewWatcher()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
defer watcher.Close()
|
|
||||||
|
|
||||||
// Start listening for events.
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case event, ok := <-watcher.Events:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println("event:", event)
|
|
||||||
if event.Has(fsnotify.Write) {
|
|
||||||
log.Println("modified file:", event.Name)
|
|
||||||
}
|
|
||||||
case err, ok := <-watcher.Errors:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
log.Println("error:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// Add a path.
|
|
||||||
err = watcher.Add("/tmp")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block main goroutine forever.
|
|
||||||
<-make(chan struct{})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Some more examples can be found in [cmd/fsnotify](cmd/fsnotify), which can be
|
|
||||||
run with:
|
|
||||||
|
|
||||||
% go run ./cmd/fsnotify
|
|
||||||
|
|
||||||
Further detailed documentation can be found in godoc:
|
|
||||||
https://pkg.go.dev/github.com/fsnotify/fsnotify
|
|
||||||
|
|
||||||
FAQ
|
|
||||||
---
|
|
||||||
### Will a file still be watched when it's moved to another directory?
|
|
||||||
No, not unless you are watching the location it was moved to.
|
|
||||||
|
|
||||||
### Are subdirectories watched?
|
|
||||||
No, you must add watches for any directory you want to watch (a recursive
|
|
||||||
watcher is on the roadmap: [#18]).
|
|
||||||
|
|
||||||
[#18]: https://github.com/fsnotify/fsnotify/issues/18
|
|
||||||
|
|
||||||
### Do I have to watch the Error and Event channels in a goroutine?
|
|
||||||
Yes. You can read both channels in the same goroutine using `select` (you don't
|
|
||||||
need a separate goroutine for both channels; see the example).
|
|
||||||
|
|
||||||
### Why don't notifications work with NFS, SMB, FUSE, /proc, or /sys?
|
|
||||||
fsnotify requires support from underlying OS to work. The current NFS and SMB
|
|
||||||
protocols does not provide network level support for file notifications, and
|
|
||||||
neither do the /proc and /sys virtual filesystems.
|
|
||||||
|
|
||||||
This could be fixed with a polling watcher ([#9]), but it's not yet implemented.
|
|
||||||
|
|
||||||
[#9]: https://github.com/fsnotify/fsnotify/issues/9
|
|
||||||
|
|
||||||
### Why do I get many Chmod events?
|
|
||||||
Some programs may generate a lot of attribute changes; for example Spotlight on
|
|
||||||
macOS, anti-virus programs, backup applications, and some others are known to do
|
|
||||||
this. As a rule, it's typically best to ignore Chmod events. They're often not
|
|
||||||
useful, and tend to cause problems.
|
|
||||||
|
|
||||||
Spotlight indexing on macOS can result in multiple events (see [#15]). A
|
|
||||||
temporary workaround is to add your folder(s) to the *Spotlight Privacy
|
|
||||||
settings* until we have a native FSEvents implementation (see [#11]).
|
|
||||||
|
|
||||||
[#11]: https://github.com/fsnotify/fsnotify/issues/11
|
|
||||||
[#15]: https://github.com/fsnotify/fsnotify/issues/15
|
|
||||||
|
|
||||||
### Watching a file doesn't work well
|
|
||||||
Watching individual files (rather than directories) is generally not recommended
|
|
||||||
as many programs (especially editors) update files atomically: it will write to
|
|
||||||
a temporary file which is then moved to to destination, overwriting the original
|
|
||||||
(or some variant thereof). The watcher on the original file is now lost, as that
|
|
||||||
no longer exists.
|
|
||||||
|
|
||||||
The upshot of this is that a power failure or crash won't leave a half-written
|
|
||||||
file.
|
|
||||||
|
|
||||||
Watch the parent directory and use `Event.Name` to filter out files you're not
|
|
||||||
interested in. There is an example of this in `cmd/fsnotify/file.go`.
|
|
||||||
|
|
||||||
Platform-specific notes
|
|
||||||
-----------------------
|
|
||||||
### Linux
|
|
||||||
When a file is removed a REMOVE event won't be emitted until all file
|
|
||||||
descriptors are closed; it will emit a CHMOD instead:
|
|
||||||
|
|
||||||
fp := os.Open("file")
|
|
||||||
os.Remove("file") // CHMOD
|
|
||||||
fp.Close() // REMOVE
|
|
||||||
|
|
||||||
This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
|
|
||||||
The `fs.inotify.max_user_watches` sysctl variable specifies the upper limit for
|
|
||||||
the number of watches per user, and `fs.inotify.max_user_instances` specifies
|
|
||||||
the maximum number of inotify instances per user. Every Watcher you create is an
|
|
||||||
"instance", and every path you add is a "watch".
|
|
||||||
|
|
||||||
These are also exposed in `/proc` as `/proc/sys/fs/inotify/max_user_watches` and
|
|
||||||
`/proc/sys/fs/inotify/max_user_instances`
|
|
||||||
|
|
||||||
To increase them you can use `sysctl` or write the value to proc file:
|
|
||||||
|
|
||||||
# The default values on Linux 5.18
|
|
||||||
sysctl fs.inotify.max_user_watches=124983
|
|
||||||
sysctl fs.inotify.max_user_instances=128
|
|
||||||
|
|
||||||
To make the changes persist on reboot edit `/etc/sysctl.conf` or
|
|
||||||
`/usr/lib/sysctl.d/50-default.conf` (details differ per Linux distro; check your
|
|
||||||
distro's documentation):
|
|
||||||
|
|
||||||
fs.inotify.max_user_watches=124983
|
|
||||||
fs.inotify.max_user_instances=128
|
|
||||||
|
|
||||||
Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
files" error.
|
|
||||||
|
|
||||||
### kqueue (macOS, all BSD systems)
|
|
||||||
kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
so if you're watching a directory with five files then that's six file
|
|
||||||
descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
these platforms.
|
|
||||||
|
|
||||||
The sysctl variables `kern.maxfiles` and `kern.maxfilesperproc` can be used to
|
|
||||||
control the maximum number of open files.
|
|
||||||
640
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
640
vendor/github.com/fsnotify/fsnotify/backend_fen.go
generated
vendored
@@ -1,640 +0,0 @@
|
|||||||
//go:build solaris
|
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
// Note: the documentation on the Watcher type and methods is generated from
|
|
||||||
// mkdoc.zsh
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
type Watcher struct {
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
Events chan Event
|
|
||||||
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
Errors chan error
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
port *unix.EventPort
|
|
||||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
dirs map[string]struct{} // Explicitly watched directories
|
|
||||||
watches map[string]struct{} // Explicitly watched non-directories
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return NewBufferedWatcher(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
|
||||||
w := &Watcher{
|
|
||||||
Events: make(chan Event, sz),
|
|
||||||
Errors: make(chan error),
|
|
||||||
dirs: make(map[string]struct{}),
|
|
||||||
watches: make(map[string]struct{}),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
w.port, err = unix.NewEventPort()
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("fsnotify.NewWatcher: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendEvent attempts to send an event to the user, returning true if the event
|
|
||||||
// was put in the channel successfully and false if the watcher has been closed.
|
|
||||||
func (w *Watcher) sendEvent(name string, op Op) (sent bool) {
|
|
||||||
select {
|
|
||||||
case w.Events <- Event{Name: name, Op: op}:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendError attempts to send an error to the user, returning true if the error
|
|
||||||
// was put in the channel successfully and false if the watcher has been closed.
|
|
||||||
func (w *Watcher) sendError(err error) (sent bool) {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) isClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
// Take the lock used by associateFile to prevent lingering events from
|
|
||||||
// being processed after the close
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
close(w.done)
|
|
||||||
return w.port.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
|
|
||||||
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
if w.port.PathIsWatched(name) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = getOptions(opts...)
|
|
||||||
|
|
||||||
// Currently we resolve symlinks that were explicitly requested to be
|
|
||||||
// watched. Otherwise we would use LStat here.
|
|
||||||
stat, err := os.Stat(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Associate all files in the directory.
|
|
||||||
if stat.IsDir() {
|
|
||||||
err := w.handleDirectory(name, stat, true, w.associateFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.dirs[name] = struct{}{}
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.associateFile(name, stat, true)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches[name] = struct{}{}
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !w.port.PathIsWatched(name) {
|
|
||||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user has expressed an intent. Immediately remove this name from
|
|
||||||
// whichever watch list it might be in. If it's not in there the delete
|
|
||||||
// doesn't cause harm.
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.watches, name)
|
|
||||||
delete(w.dirs, name)
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
stat, err := os.Stat(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove associations for every file in the directory.
|
|
||||||
if stat.IsDir() {
|
|
||||||
err := w.handleDirectory(name, stat, false, w.dissociateFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.port.DissociatePath(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents contains the main loop that runs in a goroutine watching for events.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
// If this function returns, the watcher has been closed and we can close
|
|
||||||
// these channels
|
|
||||||
defer func() {
|
|
||||||
close(w.Errors)
|
|
||||||
close(w.Events)
|
|
||||||
}()
|
|
||||||
|
|
||||||
pevents := make([]unix.PortEvent, 8)
|
|
||||||
for {
|
|
||||||
count, err := w.port.Get(pevents, 1, nil)
|
|
||||||
if err != nil && err != unix.ETIME {
|
|
||||||
// Interrupted system call (count should be 0) ignore and continue
|
|
||||||
if errors.Is(err, unix.EINTR) && count == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Get failed because we called w.Close()
|
|
||||||
if errors.Is(err, unix.EBADF) && w.isClosed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// There was an error not caused by calling w.Close()
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p := pevents[:count]
|
|
||||||
for _, pevent := range p {
|
|
||||||
if pevent.Source != unix.PORT_SOURCE_FILE {
|
|
||||||
// Event from unexpected source received; should never happen.
|
|
||||||
if !w.sendError(errors.New("Event from unexpected source received")) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.handleEvent(&pevent)
|
|
||||||
if err != nil {
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) handleDirectory(path string, stat os.FileInfo, follow bool, handler func(string, os.FileInfo, bool) error) error {
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle all children of the directory.
|
|
||||||
for _, entry := range files {
|
|
||||||
finfo, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = handler(filepath.Join(path, finfo.Name()), finfo, false)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// And finally handle the directory itself.
|
|
||||||
return handler(path, stat, follow)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleEvent might need to emit more than one fsnotify event if the events
|
|
||||||
// bitmap matches more than one event type (e.g. the file was both modified and
|
|
||||||
// had the attributes changed between when the association was created and the
|
|
||||||
// when event was returned)
|
|
||||||
func (w *Watcher) handleEvent(event *unix.PortEvent) error {
|
|
||||||
var (
|
|
||||||
events = event.Events
|
|
||||||
path = event.Path
|
|
||||||
fmode = event.Cookie.(os.FileMode)
|
|
||||||
reRegister = true
|
|
||||||
)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
_, watchedDir := w.dirs[path]
|
|
||||||
_, watchedPath := w.watches[path]
|
|
||||||
w.mu.Unlock()
|
|
||||||
isWatched := watchedDir || watchedPath
|
|
||||||
|
|
||||||
if events&unix.FILE_DELETE != 0 {
|
|
||||||
if !w.sendEvent(path, Remove) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
reRegister = false
|
|
||||||
}
|
|
||||||
if events&unix.FILE_RENAME_FROM != 0 {
|
|
||||||
if !w.sendEvent(path, Rename) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Don't keep watching the new file name
|
|
||||||
reRegister = false
|
|
||||||
}
|
|
||||||
if events&unix.FILE_RENAME_TO != 0 {
|
|
||||||
// We don't report a Rename event for this case, because Rename events
|
|
||||||
// are interpreted as referring to the _old_ name of the file, and in
|
|
||||||
// this case the event would refer to the new name of the file. This
|
|
||||||
// type of rename event is not supported by fsnotify.
|
|
||||||
|
|
||||||
// inotify reports a Remove event in this case, so we simulate this
|
|
||||||
// here.
|
|
||||||
if !w.sendEvent(path, Remove) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Don't keep watching the file that was removed
|
|
||||||
reRegister = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// The file is gone, nothing left to do.
|
|
||||||
if !reRegister {
|
|
||||||
if watchedDir {
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.dirs, path)
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
if watchedPath {
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.watches, path)
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we didn't get a deletion the file still exists and we're going to have
|
|
||||||
// to watch it again. Let's Stat it now so that we can compare permissions
|
|
||||||
// and have what we need to continue watching the file
|
|
||||||
|
|
||||||
stat, err := os.Lstat(path)
|
|
||||||
if err != nil {
|
|
||||||
// This is unexpected, but we should still emit an event. This happens
|
|
||||||
// most often on "rm -r" of a subdirectory inside a watched directory We
|
|
||||||
// get a modify event of something happening inside, but by the time we
|
|
||||||
// get here, the sudirectory is already gone. Clearly we were watching
|
|
||||||
// this path but now it is gone. Let's tell the user that it was
|
|
||||||
// removed.
|
|
||||||
if !w.sendEvent(path, Remove) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Suppress extra write events on removed directories; they are not
|
|
||||||
// informative and can be confusing.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// resolve symlinks that were explicitly watched as we would have at Add()
|
|
||||||
// time. this helps suppress spurious Chmod events on watched symlinks
|
|
||||||
if isWatched {
|
|
||||||
stat, err = os.Stat(path)
|
|
||||||
if err != nil {
|
|
||||||
// The symlink still exists, but the target is gone. Report the
|
|
||||||
// Remove similar to above.
|
|
||||||
if !w.sendEvent(path, Remove) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Don't return the error
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if events&unix.FILE_MODIFIED != 0 {
|
|
||||||
if fmode.IsDir() {
|
|
||||||
if watchedDir {
|
|
||||||
if err := w.updateDirectory(path); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !w.sendEvent(path, Write) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if !w.sendEvent(path, Write) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if events&unix.FILE_ATTRIB != 0 && stat != nil {
|
|
||||||
// Only send Chmod if perms changed
|
|
||||||
if stat.Mode().Perm() != fmode.Perm() {
|
|
||||||
if !w.sendEvent(path, Chmod) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if stat != nil {
|
|
||||||
// If we get here, it means we've hit an event above that requires us to
|
|
||||||
// continue watching the file or directory
|
|
||||||
return w.associateFile(path, stat, isWatched)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) updateDirectory(path string) error {
|
|
||||||
// The directory was modified, so we must find unwatched entities and watch
|
|
||||||
// them. If something was removed from the directory, nothing will happen,
|
|
||||||
// as everything else should still be watched.
|
|
||||||
files, err := os.ReadDir(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, entry := range files {
|
|
||||||
path := filepath.Join(path, entry.Name())
|
|
||||||
if w.port.PathIsWatched(path) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
finfo, err := entry.Info()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
err = w.associateFile(path, finfo, false)
|
|
||||||
if err != nil {
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !w.sendEvent(path, Create) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) associateFile(path string, stat os.FileInfo, follow bool) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
// This is primarily protecting the call to AssociatePath but it is
|
|
||||||
// important and intentional that the call to PathIsWatched is also
|
|
||||||
// protected by this mutex. Without this mutex, AssociatePath has been seen
|
|
||||||
// to error out that the path is already associated.
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
if w.port.PathIsWatched(path) {
|
|
||||||
// Remove the old association in favor of this one If we get ENOENT,
|
|
||||||
// then while the x/sys/unix wrapper still thought that this path was
|
|
||||||
// associated, the underlying event port did not. This call will have
|
|
||||||
// cleared up that discrepancy. The most likely cause is that the event
|
|
||||||
// has fired but we haven't processed it yet.
|
|
||||||
err := w.port.DissociatePath(path)
|
|
||||||
if err != nil && err != unix.ENOENT {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// FILE_NOFOLLOW means we watch symlinks themselves rather than their
|
|
||||||
// targets.
|
|
||||||
events := unix.FILE_MODIFIED | unix.FILE_ATTRIB | unix.FILE_NOFOLLOW
|
|
||||||
if follow {
|
|
||||||
// We *DO* follow symlinks for explicitly watched entries.
|
|
||||||
events = unix.FILE_MODIFIED | unix.FILE_ATTRIB
|
|
||||||
}
|
|
||||||
return w.port.AssociatePath(path, stat,
|
|
||||||
events,
|
|
||||||
stat.Mode())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) dissociateFile(path string, stat os.FileInfo, unused bool) error {
|
|
||||||
if !w.port.PathIsWatched(path) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return w.port.DissociatePath(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) WatchList() []string {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
entries := make([]string, 0, len(w.watches)+len(w.dirs))
|
|
||||||
for pathname := range w.dirs {
|
|
||||||
entries = append(entries, pathname)
|
|
||||||
}
|
|
||||||
for pathname := range w.watches {
|
|
||||||
entries = append(entries, pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
594
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
594
vendor/github.com/fsnotify/fsnotify/backend_inotify.go
generated
vendored
@@ -1,594 +0,0 @@
|
|||||||
//go:build linux && !appengine
|
|
||||||
// +build linux,!appengine
|
|
||||||
|
|
||||||
// Note: the documentation on the Watcher type and methods is generated from
|
|
||||||
// mkdoc.zsh
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
type Watcher struct {
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
Events chan Event
|
|
||||||
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
Errors chan error
|
|
||||||
|
|
||||||
// Store fd here as os.File.Read() will no longer return on close after
|
|
||||||
// calling Fd(). See: https://github.com/golang/go/issues/26439
|
|
||||||
fd int
|
|
||||||
inotifyFile *os.File
|
|
||||||
watches *watches
|
|
||||||
done chan struct{} // Channel for sending a "quit message" to the reader goroutine
|
|
||||||
closeMu sync.Mutex
|
|
||||||
doneResp chan struct{} // Channel to respond to Close
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
watches struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
wd map[uint32]*watch // wd → watch
|
|
||||||
path map[string]uint32 // pathname → wd
|
|
||||||
}
|
|
||||||
watch struct {
|
|
||||||
wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
|
|
||||||
flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
|
|
||||||
path string // Watch path.
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func newWatches() *watches {
|
|
||||||
return &watches{
|
|
||||||
wd: make(map[uint32]*watch),
|
|
||||||
path: make(map[string]uint32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) len() int {
|
|
||||||
w.mu.RLock()
|
|
||||||
defer w.mu.RUnlock()
|
|
||||||
return len(w.wd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) add(ww *watch) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
w.wd[ww.wd] = ww
|
|
||||||
w.path[ww.path] = ww.wd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) remove(wd uint32) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
delete(w.path, w.wd[wd].path)
|
|
||||||
delete(w.wd, wd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) removePath(path string) (uint32, bool) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
wd, ok := w.path[path]
|
|
||||||
if !ok {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(w.path, path)
|
|
||||||
delete(w.wd, wd)
|
|
||||||
|
|
||||||
return wd, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) byPath(path string) *watch {
|
|
||||||
w.mu.RLock()
|
|
||||||
defer w.mu.RUnlock()
|
|
||||||
return w.wd[w.path[path]]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) byWd(wd uint32) *watch {
|
|
||||||
w.mu.RLock()
|
|
||||||
defer w.mu.RUnlock()
|
|
||||||
return w.wd[wd]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *watches) updatePath(path string, f func(*watch) (*watch, error)) error {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
var existing *watch
|
|
||||||
wd, ok := w.path[path]
|
|
||||||
if ok {
|
|
||||||
existing = w.wd[wd]
|
|
||||||
}
|
|
||||||
|
|
||||||
upd, err := f(existing)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if upd != nil {
|
|
||||||
w.wd[upd.wd] = upd
|
|
||||||
w.path[upd.path] = upd.wd
|
|
||||||
|
|
||||||
if upd.wd != wd {
|
|
||||||
delete(w.wd, wd)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return NewBufferedWatcher(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
|
||||||
// Need to set nonblocking mode for SetDeadline to work, otherwise blocking
|
|
||||||
// I/O operations won't terminate on close.
|
|
||||||
fd, errno := unix.InotifyInit1(unix.IN_CLOEXEC | unix.IN_NONBLOCK)
|
|
||||||
if fd == -1 {
|
|
||||||
return nil, errno
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &Watcher{
|
|
||||||
fd: fd,
|
|
||||||
inotifyFile: os.NewFile(uintptr(fd), ""),
|
|
||||||
watches: newWatches(),
|
|
||||||
Events: make(chan Event, sz),
|
|
||||||
Errors: make(chan error),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
doneResp: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the event was sent, or false if watcher is closed.
|
|
||||||
func (w *Watcher) sendEvent(e Event) bool {
|
|
||||||
select {
|
|
||||||
case w.Events <- e:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the error was sent, or false if watcher is closed.
|
|
||||||
func (w *Watcher) sendError(err error) bool {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) isClosed() bool {
|
|
||||||
select {
|
|
||||||
case <-w.done:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
w.closeMu.Lock()
|
|
||||||
if w.isClosed() {
|
|
||||||
w.closeMu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
close(w.done)
|
|
||||||
w.closeMu.Unlock()
|
|
||||||
|
|
||||||
// Causes any blocking reads to return with an error, provided the file
|
|
||||||
// still supports deadline operations.
|
|
||||||
err := w.inotifyFile.Close()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for goroutine to close
|
|
||||||
<-w.doneResp
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
|
|
||||||
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
_ = getOptions(opts...)
|
|
||||||
|
|
||||||
var flags uint32 = unix.IN_MOVED_TO | unix.IN_MOVED_FROM |
|
|
||||||
unix.IN_CREATE | unix.IN_ATTRIB | unix.IN_MODIFY |
|
|
||||||
unix.IN_MOVE_SELF | unix.IN_DELETE | unix.IN_DELETE_SELF
|
|
||||||
|
|
||||||
return w.watches.updatePath(name, func(existing *watch) (*watch, error) {
|
|
||||||
if existing != nil {
|
|
||||||
flags |= existing.flags | unix.IN_MASK_ADD
|
|
||||||
}
|
|
||||||
|
|
||||||
wd, err := unix.InotifyAddWatch(w.fd, name, flags)
|
|
||||||
if wd == -1 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if existing == nil {
|
|
||||||
return &watch{
|
|
||||||
wd: uint32(wd),
|
|
||||||
path: name,
|
|
||||||
flags: flags,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
existing.wd = uint32(wd)
|
|
||||||
existing.flags = flags
|
|
||||||
return existing, nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return w.remove(filepath.Clean(name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) remove(name string) error {
|
|
||||||
wd, ok := w.watches.removePath(name)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
success, errno := unix.InotifyRmWatch(w.fd, wd)
|
|
||||||
if success == -1 {
|
|
||||||
// TODO: Perhaps it's not helpful to return an error here in every case;
|
|
||||||
// The only two possible errors are:
|
|
||||||
//
|
|
||||||
// - EBADF, which happens when w.fd is not a valid file descriptor
|
|
||||||
// of any kind.
|
|
||||||
// - EINVAL, which is when fd is not an inotify descriptor or wd
|
|
||||||
// is not a valid watch descriptor. Watch descriptors are
|
|
||||||
// invalidated when they are removed explicitly or implicitly;
|
|
||||||
// explicitly by inotify_rm_watch, implicitly when the file they
|
|
||||||
// are watching is deleted.
|
|
||||||
return errno
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) WatchList() []string {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := make([]string, 0, w.watches.len())
|
|
||||||
w.watches.mu.RLock()
|
|
||||||
for pathname := range w.watches.path {
|
|
||||||
entries = append(entries, pathname)
|
|
||||||
}
|
|
||||||
w.watches.mu.RUnlock()
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the inotify file descriptor, converts the
|
|
||||||
// received events into Event objects and sends them via the Events channel
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
defer func() {
|
|
||||||
close(w.doneResp)
|
|
||||||
close(w.Errors)
|
|
||||||
close(w.Events)
|
|
||||||
}()
|
|
||||||
|
|
||||||
var (
|
|
||||||
buf [unix.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
|
|
||||||
errno error // Syscall errno
|
|
||||||
)
|
|
||||||
for {
|
|
||||||
// See if we have been closed.
|
|
||||||
if w.isClosed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err := w.inotifyFile.Read(buf[:])
|
|
||||||
switch {
|
|
||||||
case errors.Unwrap(err) == os.ErrClosed:
|
|
||||||
return
|
|
||||||
case err != nil:
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if n < unix.SizeofInotifyEvent {
|
|
||||||
var err error
|
|
||||||
if n == 0 {
|
|
||||||
err = io.EOF // If EOF is received. This should really never happen.
|
|
||||||
} else if n < 0 {
|
|
||||||
err = errno // If an error occurred while reading.
|
|
||||||
} else {
|
|
||||||
err = errors.New("notify: short read in readEvents()") // Read was too short.
|
|
||||||
}
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32
|
|
||||||
// We don't know how many events we just read into the buffer
|
|
||||||
// While the offset points to at least one whole event...
|
|
||||||
for offset <= uint32(n-unix.SizeofInotifyEvent) {
|
|
||||||
var (
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw = (*unix.InotifyEvent)(unsafe.Pointer(&buf[offset]))
|
|
||||||
mask = uint32(raw.Mask)
|
|
||||||
nameLen = uint32(raw.Len)
|
|
||||||
)
|
|
||||||
|
|
||||||
if mask&unix.IN_Q_OVERFLOW != 0 {
|
|
||||||
if !w.sendError(ErrEventOverflow) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the event happened to the watched directory or the watched file, the kernel
|
|
||||||
// doesn't append the filename to the event, but we would like to always fill the
|
|
||||||
// the "Name" field with a valid filename. We retrieve the path of the watch from
|
|
||||||
// the "paths" map.
|
|
||||||
watch := w.watches.byWd(uint32(raw.Wd))
|
|
||||||
|
|
||||||
// inotify will automatically remove the watch on deletes; just need
|
|
||||||
// to clean our state here.
|
|
||||||
if watch != nil && mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF {
|
|
||||||
w.watches.remove(watch.wd)
|
|
||||||
}
|
|
||||||
// We can't really update the state when a watched path is moved;
|
|
||||||
// only IN_MOVE_SELF is sent and not IN_MOVED_{FROM,TO}. So remove
|
|
||||||
// the watch.
|
|
||||||
if watch != nil && mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF {
|
|
||||||
err := w.remove(watch.path)
|
|
||||||
if err != nil && !errors.Is(err, ErrNonExistentWatch) {
|
|
||||||
if !w.sendError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var name string
|
|
||||||
if watch != nil {
|
|
||||||
name = watch.path
|
|
||||||
}
|
|
||||||
if nameLen > 0 {
|
|
||||||
// Point "bytes" at the first byte of the filename
|
|
||||||
bytes := (*[unix.PathMax]byte)(unsafe.Pointer(&buf[offset+unix.SizeofInotifyEvent]))[:nameLen:nameLen]
|
|
||||||
// The filename is padded with NULL bytes. TrimRight() gets rid of those.
|
|
||||||
name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
|
|
||||||
}
|
|
||||||
|
|
||||||
event := w.newEvent(name, mask)
|
|
||||||
|
|
||||||
// Send the events that are not ignored on the events channel
|
|
||||||
if mask&unix.IN_IGNORED == 0 {
|
|
||||||
if !w.sendEvent(event) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
offset += unix.SizeofInotifyEvent + nameLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEvent returns an platform-independent Event based on an inotify mask.
|
|
||||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&unix.IN_CREATE == unix.IN_CREATE || mask&unix.IN_MOVED_TO == unix.IN_MOVED_TO {
|
|
||||||
e.Op |= Create
|
|
||||||
}
|
|
||||||
if mask&unix.IN_DELETE_SELF == unix.IN_DELETE_SELF || mask&unix.IN_DELETE == unix.IN_DELETE {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&unix.IN_MODIFY == unix.IN_MODIFY {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&unix.IN_MOVE_SELF == unix.IN_MOVE_SELF || mask&unix.IN_MOVED_FROM == unix.IN_MOVED_FROM {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
if mask&unix.IN_ATTRIB == unix.IN_ATTRIB {
|
|
||||||
e.Op |= Chmod
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
782
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
782
vendor/github.com/fsnotify/fsnotify/backend_kqueue.go
generated
vendored
@@ -1,782 +0,0 @@
|
|||||||
//go:build freebsd || openbsd || netbsd || dragonfly || darwin
|
|
||||||
// +build freebsd openbsd netbsd dragonfly darwin
|
|
||||||
|
|
||||||
// Note: the documentation on the Watcher type and methods is generated from
|
|
||||||
// mkdoc.zsh
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
type Watcher struct {
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
Events chan Event
|
|
||||||
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
Errors chan error
|
|
||||||
|
|
||||||
done chan struct{}
|
|
||||||
kq int // File descriptor (as returned by the kqueue() syscall).
|
|
||||||
closepipe [2]int // Pipe used for closing.
|
|
||||||
mu sync.Mutex // Protects access to watcher data
|
|
||||||
watches map[string]int // Watched file descriptors (key: path).
|
|
||||||
watchesByDir map[string]map[int]struct{} // Watched file descriptors indexed by the parent directory (key: dirname(path)).
|
|
||||||
userWatches map[string]struct{} // Watches added with Watcher.Add()
|
|
||||||
dirFlags map[string]uint32 // Watched directories to fflags used in kqueue.
|
|
||||||
paths map[int]pathInfo // File descriptors to path names for processing kqueue events.
|
|
||||||
fileExists map[string]struct{} // Keep track of if we know this file exists (to stop duplicate create events).
|
|
||||||
isClosed bool // Set to true when Close() is first called
|
|
||||||
}
|
|
||||||
|
|
||||||
type pathInfo struct {
|
|
||||||
name string
|
|
||||||
isDir bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return NewBufferedWatcher(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
|
||||||
kq, closepipe, err := newKqueue()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
w := &Watcher{
|
|
||||||
kq: kq,
|
|
||||||
closepipe: closepipe,
|
|
||||||
watches: make(map[string]int),
|
|
||||||
watchesByDir: make(map[string]map[int]struct{}),
|
|
||||||
dirFlags: make(map[string]uint32),
|
|
||||||
paths: make(map[int]pathInfo),
|
|
||||||
fileExists: make(map[string]struct{}),
|
|
||||||
userWatches: make(map[string]struct{}),
|
|
||||||
Events: make(chan Event, sz),
|
|
||||||
Errors: make(chan error),
|
|
||||||
done: make(chan struct{}),
|
|
||||||
}
|
|
||||||
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// newKqueue creates a new kernel event queue and returns a descriptor.
|
|
||||||
//
|
|
||||||
// This registers a new event on closepipe, which will trigger an event when
|
|
||||||
// it's closed. This way we can use kevent() without timeout/polling; without
|
|
||||||
// the closepipe, it would block forever and we wouldn't be able to stop it at
|
|
||||||
// all.
|
|
||||||
func newKqueue() (kq int, closepipe [2]int, err error) {
|
|
||||||
kq, err = unix.Kqueue()
|
|
||||||
if kq == -1 {
|
|
||||||
return kq, closepipe, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the close pipe.
|
|
||||||
err = unix.Pipe(closepipe[:])
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(kq)
|
|
||||||
return kq, closepipe, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register changes to listen on the closepipe.
|
|
||||||
changes := make([]unix.Kevent_t, 1)
|
|
||||||
// SetKevent converts int to the platform-specific types.
|
|
||||||
unix.SetKevent(&changes[0], closepipe[0], unix.EVFILT_READ,
|
|
||||||
unix.EV_ADD|unix.EV_ENABLE|unix.EV_ONESHOT)
|
|
||||||
|
|
||||||
ok, err := unix.Kevent(kq, changes, nil, nil)
|
|
||||||
if ok == -1 {
|
|
||||||
unix.Close(kq)
|
|
||||||
unix.Close(closepipe[0])
|
|
||||||
unix.Close(closepipe[1])
|
|
||||||
return kq, closepipe, err
|
|
||||||
}
|
|
||||||
return kq, closepipe, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the event was sent, or false if watcher is closed.
|
|
||||||
func (w *Watcher) sendEvent(e Event) bool {
|
|
||||||
select {
|
|
||||||
case w.Events <- e:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the error was sent, or false if watcher is closed.
|
|
||||||
func (w *Watcher) sendError(err error) bool {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
return true
|
|
||||||
case <-w.done:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
w.isClosed = true
|
|
||||||
|
|
||||||
// copy paths to remove while locked
|
|
||||||
pathsToRemove := make([]string, 0, len(w.watches))
|
|
||||||
for name := range w.watches {
|
|
||||||
pathsToRemove = append(pathsToRemove, name)
|
|
||||||
}
|
|
||||||
w.mu.Unlock() // Unlock before calling Remove, which also locks
|
|
||||||
for _, name := range pathsToRemove {
|
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine.
|
|
||||||
unix.Close(w.closepipe[1])
|
|
||||||
close(w.done)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
|
|
||||||
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
|
|
||||||
_ = getOptions(opts...)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.userWatches[name] = struct{}{}
|
|
||||||
w.mu.Unlock()
|
|
||||||
_, err := w.addWatch(name, noteAllEvents)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
return w.remove(name, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) remove(name string, unwatchFiles bool) error {
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
watchfd, ok := w.watches[name]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := w.register([]int{watchfd}, unix.EV_DELETE, 0)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
unix.Close(watchfd)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
isDir := w.paths[watchfd].isDir
|
|
||||||
delete(w.watches, name)
|
|
||||||
delete(w.userWatches, name)
|
|
||||||
|
|
||||||
parentName := filepath.Dir(name)
|
|
||||||
delete(w.watchesByDir[parentName], watchfd)
|
|
||||||
|
|
||||||
if len(w.watchesByDir[parentName]) == 0 {
|
|
||||||
delete(w.watchesByDir, parentName)
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(w.paths, watchfd)
|
|
||||||
delete(w.dirFlags, name)
|
|
||||||
delete(w.fileExists, name)
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
// Find all watched paths that are in this directory that are not external.
|
|
||||||
if unwatchFiles && isDir {
|
|
||||||
var pathsToRemove []string
|
|
||||||
w.mu.Lock()
|
|
||||||
for fd := range w.watchesByDir[name] {
|
|
||||||
path := w.paths[fd]
|
|
||||||
if _, ok := w.userWatches[path.name]; !ok {
|
|
||||||
pathsToRemove = append(pathsToRemove, path.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
for _, name := range pathsToRemove {
|
|
||||||
// Since these are internal, not much sense in propagating error to
|
|
||||||
// the user, as that will just confuse them with an error about a
|
|
||||||
// path they did not explicitly watch themselves.
|
|
||||||
w.Remove(name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) WatchList() []string {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
if w.isClosed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
entries := make([]string, 0, len(w.userWatches))
|
|
||||||
for pathname := range w.userWatches {
|
|
||||||
entries = append(entries, pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
|
|
||||||
const noteAllEvents = unix.NOTE_DELETE | unix.NOTE_WRITE | unix.NOTE_ATTRIB | unix.NOTE_RENAME
|
|
||||||
|
|
||||||
// addWatch adds name to the watched file set; the flags are interpreted as
|
|
||||||
// described in kevent(2).
|
|
||||||
//
|
|
||||||
// Returns the real path to the file which was added, with symlinks resolved.
|
|
||||||
func (w *Watcher) addWatch(name string, flags uint32) (string, error) {
|
|
||||||
var isDir bool
|
|
||||||
name = filepath.Clean(name)
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
if w.isClosed {
|
|
||||||
w.mu.Unlock()
|
|
||||||
return "", ErrClosed
|
|
||||||
}
|
|
||||||
watchfd, alreadyWatching := w.watches[name]
|
|
||||||
// We already have a watch, but we can still override flags.
|
|
||||||
if alreadyWatching {
|
|
||||||
isDir = w.paths[watchfd].isDir
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if !alreadyWatching {
|
|
||||||
fi, err := os.Lstat(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't watch sockets or named pipes
|
|
||||||
if (fi.Mode()&os.ModeSocket == os.ModeSocket) || (fi.Mode()&os.ModeNamedPipe == os.ModeNamedPipe) {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Follow Symlinks.
|
|
||||||
if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
|
|
||||||
link, err := os.Readlink(name)
|
|
||||||
if err != nil {
|
|
||||||
// Return nil because Linux can add unresolvable symlinks to the
|
|
||||||
// watch list without problems, so maintain consistency with
|
|
||||||
// that. There will be no file events for broken symlinks.
|
|
||||||
// TODO: more specific check; returns os.PathError; ENOENT?
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
_, alreadyWatching = w.watches[link]
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if alreadyWatching {
|
|
||||||
// Add to watches so we don't get spurious Create events later
|
|
||||||
// on when we diff the directories.
|
|
||||||
w.watches[name] = 0
|
|
||||||
w.fileExists[name] = struct{}{}
|
|
||||||
return link, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
name = link
|
|
||||||
fi, err = os.Lstat(name)
|
|
||||||
if err != nil {
|
|
||||||
return "", nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry on EINTR; open() can return EINTR in practice on macOS.
|
|
||||||
// See #354, and Go issues 11180 and 39237.
|
|
||||||
for {
|
|
||||||
watchfd, err = unix.Open(name, openMode, 0)
|
|
||||||
if err == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if errors.Is(err, unix.EINTR) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
isDir = fi.IsDir()
|
|
||||||
}
|
|
||||||
|
|
||||||
err := w.register([]int{watchfd}, unix.EV_ADD|unix.EV_CLEAR|unix.EV_ENABLE, flags)
|
|
||||||
if err != nil {
|
|
||||||
unix.Close(watchfd)
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !alreadyWatching {
|
|
||||||
w.mu.Lock()
|
|
||||||
parentName := filepath.Dir(name)
|
|
||||||
w.watches[name] = watchfd
|
|
||||||
|
|
||||||
watchesByDir, ok := w.watchesByDir[parentName]
|
|
||||||
if !ok {
|
|
||||||
watchesByDir = make(map[int]struct{}, 1)
|
|
||||||
w.watchesByDir[parentName] = watchesByDir
|
|
||||||
}
|
|
||||||
watchesByDir[watchfd] = struct{}{}
|
|
||||||
w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if isDir {
|
|
||||||
// Watch the directory if it has not been watched before, or if it was
|
|
||||||
// watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
|
|
||||||
w.mu.Lock()
|
|
||||||
|
|
||||||
watchDir := (flags&unix.NOTE_WRITE) == unix.NOTE_WRITE &&
|
|
||||||
(!alreadyWatching || (w.dirFlags[name]&unix.NOTE_WRITE) != unix.NOTE_WRITE)
|
|
||||||
// Store flags so this watch can be updated later
|
|
||||||
w.dirFlags[name] = flags
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if watchDir {
|
|
||||||
if err := w.watchDirectoryFiles(name); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return name, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from kqueue and converts the received kevents into
|
|
||||||
// Event values that it sends down the Events channel.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
defer func() {
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
_ = unix.Close(w.kq)
|
|
||||||
unix.Close(w.closepipe[0])
|
|
||||||
}()
|
|
||||||
|
|
||||||
eventBuffer := make([]unix.Kevent_t, 10)
|
|
||||||
for closed := false; !closed; {
|
|
||||||
kevents, err := w.read(eventBuffer)
|
|
||||||
// EINTR is okay, the syscall was interrupted before timeout expired.
|
|
||||||
if err != nil && err != unix.EINTR {
|
|
||||||
if !w.sendError(fmt.Errorf("fsnotify.readEvents: %w", err)) {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Flush the events we received to the Events channel
|
|
||||||
for _, kevent := range kevents {
|
|
||||||
var (
|
|
||||||
watchfd = int(kevent.Ident)
|
|
||||||
mask = uint32(kevent.Fflags)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Shut down the loop when the pipe is closed, but only after all
|
|
||||||
// other events have been processed.
|
|
||||||
if watchfd == w.closepipe[0] {
|
|
||||||
closed = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
path := w.paths[watchfd]
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
event := w.newEvent(path.name, mask)
|
|
||||||
|
|
||||||
if event.Has(Rename) || event.Has(Remove) {
|
|
||||||
w.remove(event.Name, false)
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.fileExists, event.Name)
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
if path.isDir && event.Has(Write) && !event.Has(Remove) {
|
|
||||||
w.sendDirectoryChangeEvents(event.Name)
|
|
||||||
} else {
|
|
||||||
if !w.sendEvent(event) {
|
|
||||||
closed = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if event.Has(Remove) {
|
|
||||||
// Look for a file that may have overwritten this; for example,
|
|
||||||
// mv f1 f2 will delete f2, then create f2.
|
|
||||||
if path.isDir {
|
|
||||||
fileDir := filepath.Clean(event.Name)
|
|
||||||
w.mu.Lock()
|
|
||||||
_, found := w.watches[fileDir]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if found {
|
|
||||||
err := w.sendDirectoryChangeEvents(fileDir)
|
|
||||||
if err != nil {
|
|
||||||
if !w.sendError(err) {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
filePath := filepath.Clean(event.Name)
|
|
||||||
if fi, err := os.Lstat(filePath); err == nil {
|
|
||||||
err := w.sendFileCreatedEventIfNew(filePath, fi)
|
|
||||||
if err != nil {
|
|
||||||
if !w.sendError(err) {
|
|
||||||
closed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newEvent returns an platform-independent Event based on kqueue Fflags.
|
|
||||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&unix.NOTE_DELETE == unix.NOTE_DELETE {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_WRITE == unix.NOTE_WRITE {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_RENAME == unix.NOTE_RENAME {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
if mask&unix.NOTE_ATTRIB == unix.NOTE_ATTRIB {
|
|
||||||
e.Op |= Chmod
|
|
||||||
}
|
|
||||||
// No point sending a write and delete event at the same time: if it's gone,
|
|
||||||
// then it's gone.
|
|
||||||
if e.Op.Has(Write) && e.Op.Has(Remove) {
|
|
||||||
e.Op &^= Write
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
// watchDirectoryFiles to mimic inotify when adding a watch on a directory
|
|
||||||
func (w *Watcher) watchDirectoryFiles(dirPath string) error {
|
|
||||||
// Get all files
|
|
||||||
files, err := os.ReadDir(dirPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
path := filepath.Join(dirPath, f.Name())
|
|
||||||
|
|
||||||
fi, err := f.Info()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("%q: %w", path, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanPath, err := w.internalWatch(path, fi)
|
|
||||||
if err != nil {
|
|
||||||
// No permission to read the file; that's not a problem: just skip.
|
|
||||||
// But do add it to w.fileExists to prevent it from being picked up
|
|
||||||
// as a "new" file later (it still shows up in the directory
|
|
||||||
// listing).
|
|
||||||
switch {
|
|
||||||
case errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM):
|
|
||||||
cleanPath = filepath.Clean(path)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("%q: %w", path, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.fileExists[cleanPath] = struct{}{}
|
|
||||||
w.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search the directory for new files and send an event for them.
|
|
||||||
//
|
|
||||||
// This functionality is to have the BSD watcher match the inotify, which sends
|
|
||||||
// a create event for files created in a watched directory.
|
|
||||||
func (w *Watcher) sendDirectoryChangeEvents(dir string) error {
|
|
||||||
files, err := os.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
// Directory no longer exists: we can ignore this safely. kqueue will
|
|
||||||
// still give us the correct events.
|
|
||||||
if errors.Is(err, os.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
fi, err := f.Info()
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.sendFileCreatedEventIfNew(filepath.Join(dir, fi.Name()), fi)
|
|
||||||
if err != nil {
|
|
||||||
// Don't need to send an error if this file isn't readable.
|
|
||||||
if errors.Is(err, unix.EACCES) || errors.Is(err, unix.EPERM) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return fmt.Errorf("fsnotify.sendDirectoryChangeEvents: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendFileCreatedEvent sends a create event if the file isn't already being tracked.
|
|
||||||
func (w *Watcher) sendFileCreatedEventIfNew(filePath string, fi os.FileInfo) (err error) {
|
|
||||||
w.mu.Lock()
|
|
||||||
_, doesExist := w.fileExists[filePath]
|
|
||||||
w.mu.Unlock()
|
|
||||||
if !doesExist {
|
|
||||||
if !w.sendEvent(Event{Name: filePath, Op: Create}) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// like watchDirectoryFiles (but without doing another ReadDir)
|
|
||||||
filePath, err = w.internalWatch(filePath, fi)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.fileExists[filePath] = struct{}{}
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) internalWatch(name string, fi os.FileInfo) (string, error) {
|
|
||||||
if fi.IsDir() {
|
|
||||||
// mimic Linux providing delete events for subdirectories, but preserve
|
|
||||||
// the flags used if currently watching subdirectory
|
|
||||||
w.mu.Lock()
|
|
||||||
flags := w.dirFlags[name]
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
flags |= unix.NOTE_DELETE | unix.NOTE_RENAME
|
|
||||||
return w.addWatch(name, flags)
|
|
||||||
}
|
|
||||||
|
|
||||||
// watch file to mimic Linux inotify
|
|
||||||
return w.addWatch(name, noteAllEvents)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register events with the queue.
|
|
||||||
func (w *Watcher) register(fds []int, flags int, fflags uint32) error {
|
|
||||||
changes := make([]unix.Kevent_t, len(fds))
|
|
||||||
for i, fd := range fds {
|
|
||||||
// SetKevent converts int to the platform-specific types.
|
|
||||||
unix.SetKevent(&changes[i], fd, unix.EVFILT_VNODE, flags)
|
|
||||||
changes[i].Fflags = fflags
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register the events.
|
|
||||||
success, err := unix.Kevent(w.kq, changes, nil, nil)
|
|
||||||
if success == -1 {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// read retrieves pending events, or waits until an event occurs.
|
|
||||||
func (w *Watcher) read(events []unix.Kevent_t) ([]unix.Kevent_t, error) {
|
|
||||||
n, err := unix.Kevent(w.kq, nil, events, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return events[0:n], nil
|
|
||||||
}
|
|
||||||
205
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
205
vendor/github.com/fsnotify/fsnotify/backend_other.go
generated
vendored
@@ -1,205 +0,0 @@
|
|||||||
//go:build appengine || (!darwin && !dragonfly && !freebsd && !openbsd && !linux && !netbsd && !solaris && !windows)
|
|
||||||
// +build appengine !darwin,!dragonfly,!freebsd,!openbsd,!linux,!netbsd,!solaris,!windows
|
|
||||||
|
|
||||||
// Note: the documentation on the Watcher type and methods is generated from
|
|
||||||
// mkdoc.zsh
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "errors"
|
|
||||||
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
type Watcher struct {
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
Events chan Event
|
|
||||||
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
Errors chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return nil, errors.New("fsnotify not supported on the current platform")
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
func NewBufferedWatcher(sz uint) (*Watcher, error) { return NewWatcher() }
|
|
||||||
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
func (w *Watcher) Close() error { return nil }
|
|
||||||
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) WatchList() []string { return nil }
|
|
||||||
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
func (w *Watcher) Add(name string) error { return nil }
|
|
||||||
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
func (w *Watcher) AddWith(name string, opts ...addOpt) error { return nil }
|
|
||||||
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) Remove(name string) error { return nil }
|
|
||||||
827
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
827
vendor/github.com/fsnotify/fsnotify/backend_windows.go
generated
vendored
@@ -1,827 +0,0 @@
|
|||||||
//go:build windows
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
// Windows backend based on ReadDirectoryChangesW()
|
|
||||||
//
|
|
||||||
// https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
|
||||||
//
|
|
||||||
// Note: the documentation on the Watcher type and methods is generated from
|
|
||||||
// mkdoc.zsh
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/windows"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\path\to\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
type Watcher struct {
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
Events chan Event
|
|
||||||
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
Errors chan error
|
|
||||||
|
|
||||||
port windows.Handle // Handle to completion port
|
|
||||||
input chan *input // Inputs to the reader are sent on this channel
|
|
||||||
quit chan chan<- error
|
|
||||||
|
|
||||||
mu sync.Mutex // Protects access to watches, closed
|
|
||||||
watches watchMap // Map of watches (key: i-number)
|
|
||||||
closed bool // Set to true when Close() is first called
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
func NewWatcher() (*Watcher, error) {
|
|
||||||
return NewBufferedWatcher(50)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
func NewBufferedWatcher(sz uint) (*Watcher, error) {
|
|
||||||
port, err := windows.CreateIoCompletionPort(windows.InvalidHandle, 0, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateIoCompletionPort", err)
|
|
||||||
}
|
|
||||||
w := &Watcher{
|
|
||||||
port: port,
|
|
||||||
watches: make(watchMap),
|
|
||||||
input: make(chan *input, 1),
|
|
||||||
Events: make(chan Event, sz),
|
|
||||||
Errors: make(chan error),
|
|
||||||
quit: make(chan chan<- error, 1),
|
|
||||||
}
|
|
||||||
go w.readEvents()
|
|
||||||
return w, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) isClosed() bool {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
return w.closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) sendEvent(name string, mask uint64) bool {
|
|
||||||
if mask == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
event := w.newEvent(name, uint32(mask))
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.quit <- ch
|
|
||||||
case w.Events <- event:
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if the error was sent, or false if watcher is closed.
|
|
||||||
func (w *Watcher) sendError(err error) bool {
|
|
||||||
select {
|
|
||||||
case w.Errors <- err:
|
|
||||||
return true
|
|
||||||
case <-w.quit:
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
func (w *Watcher) Close() error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
w.closed = true
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
// Send "quit" message to the reader goroutine
|
|
||||||
ch := make(chan error)
|
|
||||||
w.quit <- ch
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-ch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
func (w *Watcher) Add(name string) error { return w.AddWith(name) }
|
|
||||||
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
func (w *Watcher) AddWith(name string, opts ...addOpt) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
with := getOptions(opts...)
|
|
||||||
if with.bufsize < 4096 {
|
|
||||||
return fmt.Errorf("fsnotify.WithBufferSize: buffer size cannot be smaller than 4096 bytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
in := &input{
|
|
||||||
op: opAddWatch,
|
|
||||||
path: filepath.Clean(name),
|
|
||||||
flags: sysFSALLEVENTS,
|
|
||||||
reply: make(chan error),
|
|
||||||
bufsize: with.bufsize,
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) Remove(name string) error {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
in := &input{
|
|
||||||
op: opRemoveWatch,
|
|
||||||
path: filepath.Clean(name),
|
|
||||||
reply: make(chan error),
|
|
||||||
}
|
|
||||||
w.input <- in
|
|
||||||
if err := w.wakeupReader(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return <-in.reply
|
|
||||||
}
|
|
||||||
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
func (w *Watcher) WatchList() []string {
|
|
||||||
if w.isClosed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
|
|
||||||
entries := make([]string, 0, len(w.watches))
|
|
||||||
for _, entry := range w.watches {
|
|
||||||
for _, watchEntry := range entry {
|
|
||||||
entries = append(entries, watchEntry.path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries
|
|
||||||
}
|
|
||||||
|
|
||||||
// These options are from the old golang.org/x/exp/winfsnotify, where you could
|
|
||||||
// add various options to the watch. This has long since been removed.
|
|
||||||
//
|
|
||||||
// The "sys" in the name is misleading as they're not part of any "system".
|
|
||||||
//
|
|
||||||
// This should all be removed at some point, and just use windows.FILE_NOTIFY_*
|
|
||||||
const (
|
|
||||||
sysFSALLEVENTS = 0xfff
|
|
||||||
sysFSCREATE = 0x100
|
|
||||||
sysFSDELETE = 0x200
|
|
||||||
sysFSDELETESELF = 0x400
|
|
||||||
sysFSMODIFY = 0x2
|
|
||||||
sysFSMOVE = 0xc0
|
|
||||||
sysFSMOVEDFROM = 0x40
|
|
||||||
sysFSMOVEDTO = 0x80
|
|
||||||
sysFSMOVESELF = 0x800
|
|
||||||
sysFSIGNORED = 0x8000
|
|
||||||
)
|
|
||||||
|
|
||||||
func (w *Watcher) newEvent(name string, mask uint32) Event {
|
|
||||||
e := Event{Name: name}
|
|
||||||
if mask&sysFSCREATE == sysFSCREATE || mask&sysFSMOVEDTO == sysFSMOVEDTO {
|
|
||||||
e.Op |= Create
|
|
||||||
}
|
|
||||||
if mask&sysFSDELETE == sysFSDELETE || mask&sysFSDELETESELF == sysFSDELETESELF {
|
|
||||||
e.Op |= Remove
|
|
||||||
}
|
|
||||||
if mask&sysFSMODIFY == sysFSMODIFY {
|
|
||||||
e.Op |= Write
|
|
||||||
}
|
|
||||||
if mask&sysFSMOVE == sysFSMOVE || mask&sysFSMOVESELF == sysFSMOVESELF || mask&sysFSMOVEDFROM == sysFSMOVEDFROM {
|
|
||||||
e.Op |= Rename
|
|
||||||
}
|
|
||||||
return e
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
opAddWatch = iota
|
|
||||||
opRemoveWatch
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
provisional uint64 = 1 << (32 + iota)
|
|
||||||
)
|
|
||||||
|
|
||||||
type input struct {
|
|
||||||
op int
|
|
||||||
path string
|
|
||||||
flags uint32
|
|
||||||
bufsize int
|
|
||||||
reply chan error
|
|
||||||
}
|
|
||||||
|
|
||||||
type inode struct {
|
|
||||||
handle windows.Handle
|
|
||||||
volume uint32
|
|
||||||
index uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
type watch struct {
|
|
||||||
ov windows.Overlapped
|
|
||||||
ino *inode // i-number
|
|
||||||
recurse bool // Recursive watch?
|
|
||||||
path string // Directory path
|
|
||||||
mask uint64 // Directory itself is being watched with these notify flags
|
|
||||||
names map[string]uint64 // Map of names being watched and their notify flags
|
|
||||||
rename string // Remembers the old name while renaming a file
|
|
||||||
buf []byte // buffer, allocated later
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
indexMap map[uint64]*watch
|
|
||||||
watchMap map[uint32]indexMap
|
|
||||||
)
|
|
||||||
|
|
||||||
func (w *Watcher) wakeupReader() error {
|
|
||||||
err := windows.PostQueuedCompletionStatus(w.port, 0, 0, nil)
|
|
||||||
if err != nil {
|
|
||||||
return os.NewSyscallError("PostQueuedCompletionStatus", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) getDir(pathname string) (dir string, err error) {
|
|
||||||
attr, err := windows.GetFileAttributes(windows.StringToUTF16Ptr(pathname))
|
|
||||||
if err != nil {
|
|
||||||
return "", os.NewSyscallError("GetFileAttributes", err)
|
|
||||||
}
|
|
||||||
if attr&windows.FILE_ATTRIBUTE_DIRECTORY != 0 {
|
|
||||||
dir = pathname
|
|
||||||
} else {
|
|
||||||
dir, _ = filepath.Split(pathname)
|
|
||||||
dir = filepath.Clean(dir)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) getIno(path string) (ino *inode, err error) {
|
|
||||||
h, err := windows.CreateFile(windows.StringToUTF16Ptr(path),
|
|
||||||
windows.FILE_LIST_DIRECTORY,
|
|
||||||
windows.FILE_SHARE_READ|windows.FILE_SHARE_WRITE|windows.FILE_SHARE_DELETE,
|
|
||||||
nil, windows.OPEN_EXISTING,
|
|
||||||
windows.FILE_FLAG_BACKUP_SEMANTICS|windows.FILE_FLAG_OVERLAPPED, 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, os.NewSyscallError("CreateFile", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var fi windows.ByHandleFileInformation
|
|
||||||
err = windows.GetFileInformationByHandle(h, &fi)
|
|
||||||
if err != nil {
|
|
||||||
windows.CloseHandle(h)
|
|
||||||
return nil, os.NewSyscallError("GetFileInformationByHandle", err)
|
|
||||||
}
|
|
||||||
ino = &inode{
|
|
||||||
handle: h,
|
|
||||||
volume: fi.VolumeSerialNumber,
|
|
||||||
index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
|
|
||||||
}
|
|
||||||
return ino, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) get(ino *inode) *watch {
|
|
||||||
if i := m[ino.volume]; i != nil {
|
|
||||||
return i[ino.index]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (m watchMap) set(ino *inode, watch *watch) {
|
|
||||||
i := m[ino.volume]
|
|
||||||
if i == nil {
|
|
||||||
i = make(indexMap)
|
|
||||||
m[ino.volume] = i
|
|
||||||
}
|
|
||||||
i[ino.index] = watch
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) addWatch(pathname string, flags uint64, bufsize int) error {
|
|
||||||
//pathname, recurse := recursivePath(pathname)
|
|
||||||
recurse := false
|
|
||||||
|
|
||||||
dir, err := w.getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
ino, err := w.getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
watchEntry := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
if watchEntry == nil {
|
|
||||||
_, err := windows.CreateIoCompletionPort(ino.handle, w.port, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
windows.CloseHandle(ino.handle)
|
|
||||||
return os.NewSyscallError("CreateIoCompletionPort", err)
|
|
||||||
}
|
|
||||||
watchEntry = &watch{
|
|
||||||
ino: ino,
|
|
||||||
path: dir,
|
|
||||||
names: make(map[string]uint64),
|
|
||||||
recurse: recurse,
|
|
||||||
buf: make([]byte, bufsize),
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
w.watches.set(ino, watchEntry)
|
|
||||||
w.mu.Unlock()
|
|
||||||
flags |= provisional
|
|
||||||
} else {
|
|
||||||
windows.CloseHandle(ino.handle)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask |= flags
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] |= flags
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.startRead(watchEntry)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if pathname == dir {
|
|
||||||
watchEntry.mask &= ^provisional
|
|
||||||
} else {
|
|
||||||
watchEntry.names[filepath.Base(pathname)] &= ^provisional
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) remWatch(pathname string) error {
|
|
||||||
pathname, recurse := recursivePath(pathname)
|
|
||||||
|
|
||||||
dir, err := w.getDir(pathname)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ino, err := w.getIno(dir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w.mu.Lock()
|
|
||||||
watch := w.watches.get(ino)
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if recurse && !watch.recurse {
|
|
||||||
return fmt.Errorf("can't use \\... with non-recursive watch %q", pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = windows.CloseHandle(ino.handle)
|
|
||||||
if err != nil {
|
|
||||||
w.sendError(os.NewSyscallError("CloseHandle", err))
|
|
||||||
}
|
|
||||||
if watch == nil {
|
|
||||||
return fmt.Errorf("%w: %s", ErrNonExistentWatch, pathname)
|
|
||||||
}
|
|
||||||
if pathname == dir {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
||||||
watch.mask = 0
|
|
||||||
} else {
|
|
||||||
name := filepath.Base(pathname)
|
|
||||||
w.sendEvent(filepath.Join(watch.path, name), watch.names[name]&sysFSIGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.startRead(watch)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) deleteWatch(watch *watch) {
|
|
||||||
for name, mask := range watch.names {
|
|
||||||
if mask&provisional == 0 {
|
|
||||||
w.sendEvent(filepath.Join(watch.path, name), mask&sysFSIGNORED)
|
|
||||||
}
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
if watch.mask != 0 {
|
|
||||||
if watch.mask&provisional == 0 {
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSIGNORED)
|
|
||||||
}
|
|
||||||
watch.mask = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Must run within the I/O thread.
|
|
||||||
func (w *Watcher) startRead(watch *watch) error {
|
|
||||||
err := windows.CancelIo(watch.ino.handle)
|
|
||||||
if err != nil {
|
|
||||||
w.sendError(os.NewSyscallError("CancelIo", err))
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
}
|
|
||||||
mask := w.toWindowsFlags(watch.mask)
|
|
||||||
for _, m := range watch.names {
|
|
||||||
mask |= w.toWindowsFlags(m)
|
|
||||||
}
|
|
||||||
if mask == 0 {
|
|
||||||
err := windows.CloseHandle(watch.ino.handle)
|
|
||||||
if err != nil {
|
|
||||||
w.sendError(os.NewSyscallError("CloseHandle", err))
|
|
||||||
}
|
|
||||||
w.mu.Lock()
|
|
||||||
delete(w.watches[watch.ino.volume], watch.ino.index)
|
|
||||||
w.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to pass the array, rather than the slice.
|
|
||||||
hdr := (*reflect.SliceHeader)(unsafe.Pointer(&watch.buf))
|
|
||||||
rdErr := windows.ReadDirectoryChanges(watch.ino.handle,
|
|
||||||
(*byte)(unsafe.Pointer(hdr.Data)), uint32(hdr.Len),
|
|
||||||
watch.recurse, mask, nil, &watch.ov, 0)
|
|
||||||
if rdErr != nil {
|
|
||||||
err := os.NewSyscallError("ReadDirectoryChanges", rdErr)
|
|
||||||
if rdErr == windows.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
|
|
||||||
// Watched directory was probably removed
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readEvents reads from the I/O completion port, converts the
|
|
||||||
// received events into Event objects and sends them via the Events channel.
|
|
||||||
// Entry point to the I/O thread.
|
|
||||||
func (w *Watcher) readEvents() {
|
|
||||||
var (
|
|
||||||
n uint32
|
|
||||||
key uintptr
|
|
||||||
ov *windows.Overlapped
|
|
||||||
)
|
|
||||||
runtime.LockOSThread()
|
|
||||||
|
|
||||||
for {
|
|
||||||
// This error is handled after the watch == nil check below.
|
|
||||||
qErr := windows.GetQueuedCompletionStatus(w.port, &n, &key, &ov, windows.INFINITE)
|
|
||||||
|
|
||||||
watch := (*watch)(unsafe.Pointer(ov))
|
|
||||||
if watch == nil {
|
|
||||||
select {
|
|
||||||
case ch := <-w.quit:
|
|
||||||
w.mu.Lock()
|
|
||||||
var indexes []indexMap
|
|
||||||
for _, index := range w.watches {
|
|
||||||
indexes = append(indexes, index)
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
for _, index := range indexes {
|
|
||||||
for _, watch := range index {
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := windows.CloseHandle(w.port)
|
|
||||||
if err != nil {
|
|
||||||
err = os.NewSyscallError("CloseHandle", err)
|
|
||||||
}
|
|
||||||
close(w.Events)
|
|
||||||
close(w.Errors)
|
|
||||||
ch <- err
|
|
||||||
return
|
|
||||||
case in := <-w.input:
|
|
||||||
switch in.op {
|
|
||||||
case opAddWatch:
|
|
||||||
in.reply <- w.addWatch(in.path, uint64(in.flags), in.bufsize)
|
|
||||||
case opRemoveWatch:
|
|
||||||
in.reply <- w.remWatch(in.path)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
switch qErr {
|
|
||||||
case nil:
|
|
||||||
// No error
|
|
||||||
case windows.ERROR_MORE_DATA:
|
|
||||||
if watch == nil {
|
|
||||||
w.sendError(errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer"))
|
|
||||||
} else {
|
|
||||||
// The i/o succeeded but the buffer is full.
|
|
||||||
// In theory we should be building up a full packet.
|
|
||||||
// In practice we can get away with just carrying on.
|
|
||||||
n = uint32(unsafe.Sizeof(watch.buf))
|
|
||||||
}
|
|
||||||
case windows.ERROR_ACCESS_DENIED:
|
|
||||||
// Watched directory was probably removed
|
|
||||||
w.sendEvent(watch.path, watch.mask&sysFSDELETESELF)
|
|
||||||
w.deleteWatch(watch)
|
|
||||||
w.startRead(watch)
|
|
||||||
continue
|
|
||||||
case windows.ERROR_OPERATION_ABORTED:
|
|
||||||
// CancelIo was called on this handle
|
|
||||||
continue
|
|
||||||
default:
|
|
||||||
w.sendError(os.NewSyscallError("GetQueuedCompletionPort", qErr))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset uint32
|
|
||||||
for {
|
|
||||||
if n == 0 {
|
|
||||||
w.sendError(ErrEventOverflow)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// Point "raw" to the event in the buffer
|
|
||||||
raw := (*windows.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
|
|
||||||
|
|
||||||
// Create a buf that is the size of the path name
|
|
||||||
size := int(raw.FileNameLength / 2)
|
|
||||||
var buf []uint16
|
|
||||||
// TODO: Use unsafe.Slice in Go 1.17; https://stackoverflow.com/questions/51187973
|
|
||||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&buf))
|
|
||||||
sh.Data = uintptr(unsafe.Pointer(&raw.FileName))
|
|
||||||
sh.Len = size
|
|
||||||
sh.Cap = size
|
|
||||||
name := windows.UTF16ToString(buf)
|
|
||||||
fullname := filepath.Join(watch.path, name)
|
|
||||||
|
|
||||||
var mask uint64
|
|
||||||
switch raw.Action {
|
|
||||||
case windows.FILE_ACTION_REMOVED:
|
|
||||||
mask = sysFSDELETESELF
|
|
||||||
case windows.FILE_ACTION_MODIFIED:
|
|
||||||
mask = sysFSMODIFY
|
|
||||||
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
watch.rename = name
|
|
||||||
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
// Update saved path of all sub-watches.
|
|
||||||
old := filepath.Join(watch.path, watch.rename)
|
|
||||||
w.mu.Lock()
|
|
||||||
for _, watchMap := range w.watches {
|
|
||||||
for _, ww := range watchMap {
|
|
||||||
if strings.HasPrefix(ww.path, old) {
|
|
||||||
ww.path = filepath.Join(fullname, strings.TrimPrefix(ww.path, old))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.mu.Unlock()
|
|
||||||
|
|
||||||
if watch.names[watch.rename] != 0 {
|
|
||||||
watch.names[name] |= watch.names[watch.rename]
|
|
||||||
delete(watch.names, watch.rename)
|
|
||||||
mask = sysFSMOVESELF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNameEvent := func() {
|
|
||||||
w.sendEvent(fullname, watch.names[name]&mask)
|
|
||||||
}
|
|
||||||
if raw.Action != windows.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
if raw.Action == windows.FILE_ACTION_REMOVED {
|
|
||||||
w.sendEvent(fullname, watch.names[name]&sysFSIGNORED)
|
|
||||||
delete(watch.names, name)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.sendEvent(fullname, watch.mask&w.toFSnotifyFlags(raw.Action))
|
|
||||||
if raw.Action == windows.FILE_ACTION_RENAMED_NEW_NAME {
|
|
||||||
fullname = filepath.Join(watch.path, watch.rename)
|
|
||||||
sendNameEvent()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move to the next event in the buffer
|
|
||||||
if raw.NextEntryOffset == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
offset += raw.NextEntryOffset
|
|
||||||
|
|
||||||
// Error!
|
|
||||||
if offset >= n {
|
|
||||||
//lint:ignore ST1005 Windows should be capitalized
|
|
||||||
w.sendError(errors.New(
|
|
||||||
"Windows system assumed buffer larger than it is, events have likely been missed"))
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := w.startRead(watch); err != nil {
|
|
||||||
w.sendError(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) toWindowsFlags(mask uint64) uint32 {
|
|
||||||
var m uint32
|
|
||||||
if mask&sysFSMODIFY != 0 {
|
|
||||||
m |= windows.FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
||||||
}
|
|
||||||
if mask&(sysFSMOVE|sysFSCREATE|sysFSDELETE) != 0 {
|
|
||||||
m |= windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Watcher) toFSnotifyFlags(action uint32) uint64 {
|
|
||||||
switch action {
|
|
||||||
case windows.FILE_ACTION_ADDED:
|
|
||||||
return sysFSCREATE
|
|
||||||
case windows.FILE_ACTION_REMOVED:
|
|
||||||
return sysFSDELETE
|
|
||||||
case windows.FILE_ACTION_MODIFIED:
|
|
||||||
return sysFSMODIFY
|
|
||||||
case windows.FILE_ACTION_RENAMED_OLD_NAME:
|
|
||||||
return sysFSMOVEDFROM
|
|
||||||
case windows.FILE_ACTION_RENAMED_NEW_NAME:
|
|
||||||
return sysFSMOVEDTO
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
146
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
146
vendor/github.com/fsnotify/fsnotify/fsnotify.go
generated
vendored
@@ -1,146 +0,0 @@
|
|||||||
// Package fsnotify provides a cross-platform interface for file system
|
|
||||||
// notifications.
|
|
||||||
//
|
|
||||||
// Currently supported systems:
|
|
||||||
//
|
|
||||||
// Linux 2.6.32+ via inotify
|
|
||||||
// BSD, macOS via kqueue
|
|
||||||
// Windows via ReadDirectoryChangesW
|
|
||||||
// illumos via FEN
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Event represents a file system notification.
|
|
||||||
type Event struct {
|
|
||||||
// Path to the file or directory.
|
|
||||||
//
|
|
||||||
// Paths are relative to the input; for example with Add("dir") the Name
|
|
||||||
// will be set to "dir/file" if you create that file, but if you use
|
|
||||||
// Add("/path/to/dir") it will be "/path/to/dir/file".
|
|
||||||
Name string
|
|
||||||
|
|
||||||
// File operation that triggered the event.
|
|
||||||
//
|
|
||||||
// This is a bitmask and some systems may send multiple operations at once.
|
|
||||||
// Use the Event.Has() method instead of comparing with ==.
|
|
||||||
Op Op
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op describes a set of file operations.
|
|
||||||
type Op uint32
|
|
||||||
|
|
||||||
// The operations fsnotify can trigger; see the documentation on [Watcher] for a
|
|
||||||
// full description, and check them with [Event.Has].
|
|
||||||
const (
|
|
||||||
// A new pathname was created.
|
|
||||||
Create Op = 1 << iota
|
|
||||||
|
|
||||||
// The pathname was written to; this does *not* mean the write has finished,
|
|
||||||
// and a write can be followed by more writes.
|
|
||||||
Write
|
|
||||||
|
|
||||||
// The path was removed; any watches on it will be removed. Some "remove"
|
|
||||||
// operations may trigger a Rename if the file is actually moved (for
|
|
||||||
// example "remove to trash" is often a rename).
|
|
||||||
Remove
|
|
||||||
|
|
||||||
// The path was renamed to something else; any watched on it will be
|
|
||||||
// removed.
|
|
||||||
Rename
|
|
||||||
|
|
||||||
// File attributes were changed.
|
|
||||||
//
|
|
||||||
// It's generally not recommended to take action on this event, as it may
|
|
||||||
// get triggered very frequently by some software. For example, Spotlight
|
|
||||||
// indexing on macOS, anti-virus software, backup software, etc.
|
|
||||||
Chmod
|
|
||||||
)
|
|
||||||
|
|
||||||
// Common errors that can be reported.
|
|
||||||
var (
|
|
||||||
ErrNonExistentWatch = errors.New("fsnotify: can't remove non-existent watch")
|
|
||||||
ErrEventOverflow = errors.New("fsnotify: queue or buffer overflow")
|
|
||||||
ErrClosed = errors.New("fsnotify: watcher already closed")
|
|
||||||
)
|
|
||||||
|
|
||||||
func (o Op) String() string {
|
|
||||||
var b strings.Builder
|
|
||||||
if o.Has(Create) {
|
|
||||||
b.WriteString("|CREATE")
|
|
||||||
}
|
|
||||||
if o.Has(Remove) {
|
|
||||||
b.WriteString("|REMOVE")
|
|
||||||
}
|
|
||||||
if o.Has(Write) {
|
|
||||||
b.WriteString("|WRITE")
|
|
||||||
}
|
|
||||||
if o.Has(Rename) {
|
|
||||||
b.WriteString("|RENAME")
|
|
||||||
}
|
|
||||||
if o.Has(Chmod) {
|
|
||||||
b.WriteString("|CHMOD")
|
|
||||||
}
|
|
||||||
if b.Len() == 0 {
|
|
||||||
return "[no events]"
|
|
||||||
}
|
|
||||||
return b.String()[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Has reports if this operation has the given operation.
|
|
||||||
func (o Op) Has(h Op) bool { return o&h != 0 }
|
|
||||||
|
|
||||||
// Has reports if this event has the given operation.
|
|
||||||
func (e Event) Has(op Op) bool { return e.Op.Has(op) }
|
|
||||||
|
|
||||||
// String returns a string representation of the event with their path.
|
|
||||||
func (e Event) String() string {
|
|
||||||
return fmt.Sprintf("%-13s %q", e.Op.String(), e.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
type (
|
|
||||||
addOpt func(opt *withOpts)
|
|
||||||
withOpts struct {
|
|
||||||
bufsize int
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
var defaultOpts = withOpts{
|
|
||||||
bufsize: 65536, // 64K
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOptions(opts ...addOpt) withOpts {
|
|
||||||
with := defaultOpts
|
|
||||||
for _, o := range opts {
|
|
||||||
o(&with)
|
|
||||||
}
|
|
||||||
return with
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithBufferSize sets the [ReadDirectoryChangesW] buffer size.
|
|
||||||
//
|
|
||||||
// This only has effect on Windows systems, and is a no-op for other backends.
|
|
||||||
//
|
|
||||||
// The default value is 64K (65536 bytes) which is the highest value that works
|
|
||||||
// on all filesystems and should be enough for most applications, but if you
|
|
||||||
// have a large burst of events it may not be enough. You can increase it if
|
|
||||||
// you're hitting "queue or buffer overflow" errors ([ErrEventOverflow]).
|
|
||||||
//
|
|
||||||
// [ReadDirectoryChangesW]: https://learn.microsoft.com/en-gb/windows/win32/api/winbase/nf-winbase-readdirectorychangesw
|
|
||||||
func WithBufferSize(bytes int) addOpt {
|
|
||||||
return func(opt *withOpts) { opt.bufsize = bytes }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this path is recursive (ends with "/..." or "\..."), and return the
|
|
||||||
// path with the /... stripped.
|
|
||||||
func recursivePath(path string) (string, bool) {
|
|
||||||
if filepath.Base(path) == "..." {
|
|
||||||
return filepath.Dir(path), true
|
|
||||||
}
|
|
||||||
return path, false
|
|
||||||
}
|
|
||||||
259
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
259
vendor/github.com/fsnotify/fsnotify/mkdoc.zsh
generated
vendored
@@ -1,259 +0,0 @@
|
|||||||
#!/usr/bin/env zsh
|
|
||||||
[ "${ZSH_VERSION:-}" = "" ] && echo >&2 "Only works with zsh" && exit 1
|
|
||||||
setopt err_exit no_unset pipefail extended_glob
|
|
||||||
|
|
||||||
# Simple script to update the godoc comments on all watchers so you don't need
|
|
||||||
# to update the same comment 5 times.
|
|
||||||
|
|
||||||
watcher=$(<<EOF
|
|
||||||
// Watcher watches a set of paths, delivering events on a channel.
|
|
||||||
//
|
|
||||||
// A watcher should not be copied (e.g. pass it by pointer, rather than by
|
|
||||||
// value).
|
|
||||||
//
|
|
||||||
// # Linux notes
|
|
||||||
//
|
|
||||||
// When a file is removed a Remove event won't be emitted until all file
|
|
||||||
// descriptors are closed, and deletes will always emit a Chmod. For example:
|
|
||||||
//
|
|
||||||
// fp := os.Open("file")
|
|
||||||
// os.Remove("file") // Triggers Chmod
|
|
||||||
// fp.Close() // Triggers Remove
|
|
||||||
//
|
|
||||||
// This is the event that inotify sends, so not much can be changed about this.
|
|
||||||
//
|
|
||||||
// The fs.inotify.max_user_watches sysctl variable specifies the upper limit
|
|
||||||
// for the number of watches per user, and fs.inotify.max_user_instances
|
|
||||||
// specifies the maximum number of inotify instances per user. Every Watcher you
|
|
||||||
// create is an "instance", and every path you add is a "watch".
|
|
||||||
//
|
|
||||||
// These are also exposed in /proc as /proc/sys/fs/inotify/max_user_watches and
|
|
||||||
// /proc/sys/fs/inotify/max_user_instances
|
|
||||||
//
|
|
||||||
// To increase them you can use sysctl or write the value to the /proc file:
|
|
||||||
//
|
|
||||||
// # Default values on Linux 5.18
|
|
||||||
// sysctl fs.inotify.max_user_watches=124983
|
|
||||||
// sysctl fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// To make the changes persist on reboot edit /etc/sysctl.conf or
|
|
||||||
// /usr/lib/sysctl.d/50-default.conf (details differ per Linux distro; check
|
|
||||||
// your distro's documentation):
|
|
||||||
//
|
|
||||||
// fs.inotify.max_user_watches=124983
|
|
||||||
// fs.inotify.max_user_instances=128
|
|
||||||
//
|
|
||||||
// Reaching the limit will result in a "no space left on device" or "too many open
|
|
||||||
// files" error.
|
|
||||||
//
|
|
||||||
// # kqueue notes (macOS, BSD)
|
|
||||||
//
|
|
||||||
// kqueue requires opening a file descriptor for every file that's being watched;
|
|
||||||
// so if you're watching a directory with five files then that's six file
|
|
||||||
// descriptors. You will run in to your system's "max open files" limit faster on
|
|
||||||
// these platforms.
|
|
||||||
//
|
|
||||||
// The sysctl variables kern.maxfiles and kern.maxfilesperproc can be used to
|
|
||||||
// control the maximum number of open files, as well as /etc/login.conf on BSD
|
|
||||||
// systems.
|
|
||||||
//
|
|
||||||
// # Windows notes
|
|
||||||
//
|
|
||||||
// Paths can be added as "C:\\path\\to\\dir", but forward slashes
|
|
||||||
// ("C:/path/to/dir") will also work.
|
|
||||||
//
|
|
||||||
// When a watched directory is removed it will always send an event for the
|
|
||||||
// directory itself, but may not send events for all files in that directory.
|
|
||||||
// Sometimes it will send events for all times, sometimes it will send no
|
|
||||||
// events, and often only for some files.
|
|
||||||
//
|
|
||||||
// The default ReadDirectoryChangesW() buffer size is 64K, which is the largest
|
|
||||||
// value that is guaranteed to work with SMB filesystems. If you have many
|
|
||||||
// events in quick succession this may not be enough, and you will have to use
|
|
||||||
// [WithBufferSize] to increase the value.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
new=$(<<EOF
|
|
||||||
// NewWatcher creates a new Watcher.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
newbuffered=$(<<EOF
|
|
||||||
// NewBufferedWatcher creates a new Watcher with a buffered Watcher.Events
|
|
||||||
// channel.
|
|
||||||
//
|
|
||||||
// The main use case for this is situations with a very large number of events
|
|
||||||
// where the kernel buffer size can't be increased (e.g. due to lack of
|
|
||||||
// permissions). An unbuffered Watcher will perform better for almost all use
|
|
||||||
// cases, and whenever possible you will be better off increasing the kernel
|
|
||||||
// buffers instead of adding a large userspace buffer.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
add=$(<<EOF
|
|
||||||
// Add starts monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// A path can only be watched once; watching it more than once is a no-op and will
|
|
||||||
// not return an error. Paths that do not yet exist on the filesystem cannot be
|
|
||||||
// watched.
|
|
||||||
//
|
|
||||||
// A watch will be automatically removed if the watched path is deleted or
|
|
||||||
// renamed. The exception is the Windows backend, which doesn't remove the
|
|
||||||
// watcher on renames.
|
|
||||||
//
|
|
||||||
// Notifications on network filesystems (NFS, SMB, FUSE, etc.) or special
|
|
||||||
// filesystems (/proc, /sys, etc.) generally don't work.
|
|
||||||
//
|
|
||||||
// Returns [ErrClosed] if [Watcher.Close] was called.
|
|
||||||
//
|
|
||||||
// See [Watcher.AddWith] for a version that allows adding options.
|
|
||||||
//
|
|
||||||
// # Watching directories
|
|
||||||
//
|
|
||||||
// All files in a directory are monitored, including new files that are created
|
|
||||||
// after the watcher is started. Subdirectories are not watched (i.e. it's
|
|
||||||
// non-recursive).
|
|
||||||
//
|
|
||||||
// # Watching files
|
|
||||||
//
|
|
||||||
// Watching individual files (rather than directories) is generally not
|
|
||||||
// recommended as many programs (especially editors) update files atomically: it
|
|
||||||
// will write to a temporary file which is then moved to to destination,
|
|
||||||
// overwriting the original (or some variant thereof). The watcher on the
|
|
||||||
// original file is now lost, as that no longer exists.
|
|
||||||
//
|
|
||||||
// The upshot of this is that a power failure or crash won't leave a
|
|
||||||
// half-written file.
|
|
||||||
//
|
|
||||||
// Watch the parent directory and use Event.Name to filter out files you're not
|
|
||||||
// interested in. There is an example of this in cmd/fsnotify/file.go.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
addwith=$(<<EOF
|
|
||||||
// AddWith is like [Watcher.Add], but allows adding options. When using Add()
|
|
||||||
// the defaults described below are used.
|
|
||||||
//
|
|
||||||
// Possible options are:
|
|
||||||
//
|
|
||||||
// - [WithBufferSize] sets the buffer size for the Windows backend; no-op on
|
|
||||||
// other platforms. The default is 64K (65536 bytes).
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
remove=$(<<EOF
|
|
||||||
// Remove stops monitoring the path for changes.
|
|
||||||
//
|
|
||||||
// Directories are always removed non-recursively. For example, if you added
|
|
||||||
// /tmp/dir and /tmp/dir/subdir then you will need to remove both.
|
|
||||||
//
|
|
||||||
// Removing a path that has not yet been added returns [ErrNonExistentWatch].
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
close=$(<<EOF
|
|
||||||
// Close removes all watches and closes the Events channel.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
watchlist=$(<<EOF
|
|
||||||
// WatchList returns all paths explicitly added with [Watcher.Add] (and are not
|
|
||||||
// yet removed).
|
|
||||||
//
|
|
||||||
// Returns nil if [Watcher.Close] was called.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
events=$(<<EOF
|
|
||||||
// Events sends the filesystem change events.
|
|
||||||
//
|
|
||||||
// fsnotify can send the following events; a "path" here can refer to a
|
|
||||||
// file, directory, symbolic link, or special file like a FIFO.
|
|
||||||
//
|
|
||||||
// fsnotify.Create A new path was created; this may be followed by one
|
|
||||||
// or more Write events if data also gets written to a
|
|
||||||
// file.
|
|
||||||
//
|
|
||||||
// fsnotify.Remove A path was removed.
|
|
||||||
//
|
|
||||||
// fsnotify.Rename A path was renamed. A rename is always sent with the
|
|
||||||
// old path as Event.Name, and a Create event will be
|
|
||||||
// sent with the new name. Renames are only sent for
|
|
||||||
// paths that are currently watched; e.g. moving an
|
|
||||||
// unmonitored file into a monitored directory will
|
|
||||||
// show up as just a Create. Similarly, renaming a file
|
|
||||||
// to outside a monitored directory will show up as
|
|
||||||
// only a Rename.
|
|
||||||
//
|
|
||||||
// fsnotify.Write A file or named pipe was written to. A Truncate will
|
|
||||||
// also trigger a Write. A single "write action"
|
|
||||||
// initiated by the user may show up as one or multiple
|
|
||||||
// writes, depending on when the system syncs things to
|
|
||||||
// disk. For example when compiling a large Go program
|
|
||||||
// you may get hundreds of Write events, and you may
|
|
||||||
// want to wait until you've stopped receiving them
|
|
||||||
// (see the dedup example in cmd/fsnotify).
|
|
||||||
//
|
|
||||||
// Some systems may send Write event for directories
|
|
||||||
// when the directory content changes.
|
|
||||||
//
|
|
||||||
// fsnotify.Chmod Attributes were changed. On Linux this is also sent
|
|
||||||
// when a file is removed (or more accurately, when a
|
|
||||||
// link to an inode is removed). On kqueue it's sent
|
|
||||||
// when a file is truncated. On Windows it's never
|
|
||||||
// sent.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
errors=$(<<EOF
|
|
||||||
// Errors sends any errors.
|
|
||||||
//
|
|
||||||
// ErrEventOverflow is used to indicate there are too many events:
|
|
||||||
//
|
|
||||||
// - inotify: There are too many queued events (fs.inotify.max_queued_events sysctl)
|
|
||||||
// - windows: The buffer size is too small; WithBufferSize() can be used to increase it.
|
|
||||||
// - kqueue, fen: Not used.
|
|
||||||
EOF
|
|
||||||
)
|
|
||||||
|
|
||||||
set-cmt() {
|
|
||||||
local pat=$1
|
|
||||||
local cmt=$2
|
|
||||||
|
|
||||||
IFS=$'\n' local files=($(grep -n $pat backend_*~*_test.go))
|
|
||||||
for f in $files; do
|
|
||||||
IFS=':' local fields=($=f)
|
|
||||||
local file=$fields[1]
|
|
||||||
local end=$(( $fields[2] - 1 ))
|
|
||||||
|
|
||||||
# Find start of comment.
|
|
||||||
local start=0
|
|
||||||
IFS=$'\n' local lines=($(head -n$end $file))
|
|
||||||
for (( i = 1; i <= $#lines; i++ )); do
|
|
||||||
local line=$lines[-$i]
|
|
||||||
if ! grep -q '^[[:space:]]*//' <<<$line; then
|
|
||||||
start=$(( end - (i - 2) ))
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
head -n $(( start - 1 )) $file >/tmp/x
|
|
||||||
print -r -- $cmt >>/tmp/x
|
|
||||||
tail -n+$(( end + 1 )) $file >>/tmp/x
|
|
||||||
mv /tmp/x $file
|
|
||||||
done
|
|
||||||
}
|
|
||||||
|
|
||||||
set-cmt '^type Watcher struct ' $watcher
|
|
||||||
set-cmt '^func NewWatcher(' $new
|
|
||||||
set-cmt '^func NewBufferedWatcher(' $newbuffered
|
|
||||||
set-cmt '^func (w \*Watcher) Add(' $add
|
|
||||||
set-cmt '^func (w \*Watcher) AddWith(' $addwith
|
|
||||||
set-cmt '^func (w \*Watcher) Remove(' $remove
|
|
||||||
set-cmt '^func (w \*Watcher) Close(' $close
|
|
||||||
set-cmt '^func (w \*Watcher) WatchList(' $watchlist
|
|
||||||
set-cmt '^[[:space:]]*Events *chan Event$' $events
|
|
||||||
set-cmt '^[[:space:]]*Errors *chan error$' $errors
|
|
||||||
8
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
8
vendor/github.com/fsnotify/fsnotify/system_bsd.go
generated
vendored
@@ -1,8 +0,0 @@
|
|||||||
//go:build freebsd || openbsd || netbsd || dragonfly
|
|
||||||
// +build freebsd openbsd netbsd dragonfly
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
const openMode = unix.O_NONBLOCK | unix.O_RDONLY | unix.O_CLOEXEC
|
|
||||||
9
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
9
vendor/github.com/fsnotify/fsnotify/system_darwin.go
generated
vendored
@@ -1,9 +0,0 @@
|
|||||||
//go:build darwin
|
|
||||||
// +build darwin
|
|
||||||
|
|
||||||
package fsnotify
|
|
||||||
|
|
||||||
import "golang.org/x/sys/unix"
|
|
||||||
|
|
||||||
// note: this constant is not defined on BSD
|
|
||||||
const openMode = unix.O_EVTONLY | unix.O_CLOEXEC
|
|
||||||
23
vendor/github.com/go-fonts/liberation/LICENSE
generated
vendored
23
vendor/github.com/go-fonts/liberation/LICENSE
generated
vendored
@@ -1,23 +0,0 @@
|
|||||||
Copyright ©2020 The go-fonts Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer in the
|
|
||||||
documentation and/or other materials provided with the distribution.
|
|
||||||
* Neither the name of the go-fonts project nor the names of its authors and
|
|
||||||
contributors may be used to endorse or promote products derived from this
|
|
||||||
software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
||||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
||||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
||||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
||||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
101
vendor/github.com/go-fonts/liberation/LICENSE-SIL
generated
vendored
101
vendor/github.com/go-fonts/liberation/LICENSE-SIL
generated
vendored
@@ -1,101 +0,0 @@
|
|||||||
Digitized data copyright (c) 2010 Google Corporation
|
|
||||||
with Reserved Font Arimo, Tinos and Cousine.
|
|
||||||
Copyright (c) 2012 Red Hat, Inc.
|
|
||||||
with Reserved Font Name Liberation.
|
|
||||||
|
|
||||||
This Font Software is licensed under the SIL Open Font License,
|
|
||||||
Version 1.1.
|
|
||||||
|
|
||||||
This license is copied below, and is also available with a FAQ at:
|
|
||||||
http://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
|
||||||
|
|
||||||
PREAMBLE The goals of the Open Font License (OFL) are to stimulate
|
|
||||||
worldwide development of collaborative font projects, to support the font
|
|
||||||
creation efforts of academic and linguistic communities, and to provide
|
|
||||||
a free and open framework in which fonts may be shared and improved in
|
|
||||||
partnership with others.
|
|
||||||
|
|
||||||
The OFL allows the licensed fonts to be used, studied, modified and
|
|
||||||
redistributed freely as long as they are not sold by themselves.
|
|
||||||
The fonts, including any derivative works, can be bundled, embedded,
|
|
||||||
redistributed and/or sold with any software provided that any reserved
|
|
||||||
names are not used by derivative works. The fonts and derivatives,
|
|
||||||
however, cannot be released under any other type of license. The
|
|
||||||
requirement for fonts to remain under this license does not apply to
|
|
||||||
any document created using the fonts or their derivatives.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DEFINITIONS
|
|
||||||
"Font Software" refers to the set of files released by the Copyright
|
|
||||||
Holder(s) under this license and clearly marked as such.
|
|
||||||
This may include source files, build scripts and documentation.
|
|
||||||
|
|
||||||
"Reserved Font Name" refers to any names specified as such after the
|
|
||||||
copyright statement(s).
|
|
||||||
|
|
||||||
"Original Version" refers to the collection of Font Software components
|
|
||||||
as distributed by the Copyright Holder(s).
|
|
||||||
|
|
||||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
|
||||||
or substituting ? in part or in whole ?
|
|
||||||
any of the components of the Original Version, by changing formats or
|
|
||||||
by porting the Font Software to a new environment.
|
|
||||||
|
|
||||||
"Author" refers to any designer, engineer, programmer, technical writer
|
|
||||||
or other person who contributed to the Font Software.
|
|
||||||
|
|
||||||
|
|
||||||
PERMISSION & CONDITIONS
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a
|
|
||||||
copy of the Font Software, to use, study, copy, merge, embed, modify,
|
|
||||||
redistribute, and sell modified and unmodified copies of the Font
|
|
||||||
Software, subject to the following conditions:
|
|
||||||
|
|
||||||
1) Neither the Font Software nor any of its individual components,in
|
|
||||||
Original or Modified Versions, may be sold by itself.
|
|
||||||
|
|
||||||
2) Original or Modified Versions of the Font Software may be bundled,
|
|
||||||
redistributed and/or sold with any software, provided that each copy
|
|
||||||
contains the above copyright notice and this license. These can be
|
|
||||||
included either as stand-alone text files, human-readable headers or
|
|
||||||
in the appropriate machine-readable metadata fields within text or
|
|
||||||
binary files as long as those fields can be easily viewed by the user.
|
|
||||||
|
|
||||||
3) No Modified Version of the Font Software may use the Reserved Font
|
|
||||||
Name(s) unless explicit written permission is granted by the
|
|
||||||
corresponding Copyright Holder. This restriction only applies to the
|
|
||||||
primary font name as presented to the users.
|
|
||||||
|
|
||||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
|
||||||
Software shall not be used to promote, endorse or advertise any
|
|
||||||
Modified Version, except to acknowledge the contribution(s) of the
|
|
||||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
|
||||||
permission.
|
|
||||||
|
|
||||||
5) The Font Software, modified or unmodified, in part or in whole, must
|
|
||||||
be distributed entirely under this license, and must not be distributed
|
|
||||||
under any other license. The requirement for fonts to remain under
|
|
||||||
this license does not apply to any document created using the Font
|
|
||||||
Software.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
TERMINATION
|
|
||||||
This license becomes null and void if any of the above conditions are not met.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DISCLAIMER
|
|
||||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
|
||||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
|
||||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
|
||||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
|
||||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
||||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER
|
|
||||||
DEALINGS IN THE FONT SOFTWARE.
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationmonobold/LiberationMono-Bold.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationmonobold/LiberationMono-Bold.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationmonobold/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationmonobold/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationmonobold provides the "LiberationMono Bold" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationmonobold // import "github.com/go-fonts/liberation/liberationmonobold"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationMono Bold" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationMono-Bold.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationmonobolditalic/LiberationMono-BoldItalic.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationmonobolditalic/LiberationMono-BoldItalic.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationmonobolditalic/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationmonobolditalic/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationmonobolditalic provides the "LiberationMono BoldItalic" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationmonobolditalic // import "github.com/go-fonts/liberation/liberationmonobolditalic"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationMono BoldItalic" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationMono-BoldItalic.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationmonoitalic/LiberationMono-Italic.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationmonoitalic/LiberationMono-Italic.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationmonoitalic/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationmonoitalic/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationmonoitalic provides the "LiberationMono Italic" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationmonoitalic // import "github.com/go-fonts/liberation/liberationmonoitalic"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationMono Italic" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationMono-Italic.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationmonoregular/LiberationMono-Regular.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationmonoregular/LiberationMono-Regular.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationmonoregular/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationmonoregular/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationmonoregular provides the "LiberationMono Regular" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationmonoregular // import "github.com/go-fonts/liberation/liberationmonoregular"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationMono Regular" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationMono-Regular.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationsansbold/LiberationSans-Bold.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationsansbold/LiberationSans-Bold.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationsansbold/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationsansbold/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationsansbold provides the "LiberationSans Bold" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationsansbold // import "github.com/go-fonts/liberation/liberationsansbold"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationSans Bold" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationSans-Bold.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationsansbolditalic/LiberationSans-BoldItalic.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationsansbolditalic/LiberationSans-BoldItalic.ttf
generated
vendored
Binary file not shown.
12
vendor/github.com/go-fonts/liberation/liberationsansbolditalic/data.go
generated
vendored
12
vendor/github.com/go-fonts/liberation/liberationsansbolditalic/data.go
generated
vendored
@@ -1,12 +0,0 @@
|
|||||||
// generated by go run gen-fonts.go; DO NOT EDIT
|
|
||||||
|
|
||||||
// Package liberationsansbolditalic provides the "LiberationSans BoldItalic" TrueType font
|
|
||||||
// from the Liberation font family.
|
|
||||||
package liberationsansbolditalic // import "github.com/go-fonts/liberation/liberationsansbolditalic"
|
|
||||||
|
|
||||||
import _ "embed"
|
|
||||||
|
|
||||||
// TTF is the data for the "LiberationSans BoldItalic" TrueType font.
|
|
||||||
//
|
|
||||||
//go:embed LiberationSans-BoldItalic.ttf
|
|
||||||
var TTF []byte
|
|
||||||
BIN
vendor/github.com/go-fonts/liberation/liberationsansitalic/LiberationSans-Italic.ttf
generated
vendored
BIN
vendor/github.com/go-fonts/liberation/liberationsansitalic/LiberationSans-Italic.ttf
generated
vendored
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user