mirror of
https://github.com/TomWright/dasel.git
synced 2022-05-22 02:32:45 +03:00
Initial commit
This commit is contained in:
42
.github/workflows/build.yaml
vendored
Normal file
42
.github/workflows/build.yaml
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*.*.*'
|
||||
name: Build
|
||||
jobs:
|
||||
publish:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.15.x]
|
||||
os:
|
||||
- ubuntu-latest
|
||||
- windows-latest
|
||||
- macos-latest
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
artifact_name: dasel
|
||||
asset_name: dasel_linux_amd64
|
||||
- os: windows-latest
|
||||
artifact_name: dasel.exe
|
||||
asset_name: dasel_windows_amd64.exe
|
||||
- os: macos-latest
|
||||
artifact_name: dasel
|
||||
asset_name: dasel_macos_amd64
|
||||
name: Publish for ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-go@v2-beta
|
||||
with:
|
||||
go-version: '^1.15.0' # The Go version to download (if necessary) and use.
|
||||
- name: Set env
|
||||
run: echo ::set-env name=RELEASE_VERSION::${GITHUB_REF:10}
|
||||
- name: Build
|
||||
run: go build -o target/release/${{ matrix.artifact_name }} -ldflags="-X 'github.com/tomwright/dasel/internal.Version=${{ env.RELEASE_VERSION }}'" ./cmd/dasel
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v1-release
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: target/release/${{ matrix.artifact_name }}
|
||||
asset_name: ${{ matrix.asset_name }}
|
||||
tag: ${{ github.ref }}
|
||||
24
.github/workflows/test.yaml
vendored
Normal file
24
.github/workflows/test.yaml
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
on: [push, pull_request]
|
||||
name: Test
|
||||
jobs:
|
||||
test:
|
||||
strategy:
|
||||
matrix:
|
||||
go-version: [1.15.x]
|
||||
platform: [ubuntu-latest]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
- name: Install Go
|
||||
uses: actions/setup-go@v1
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v1
|
||||
- uses: actions/cache@v1
|
||||
with:
|
||||
path: ~/go/pkg/mod
|
||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-
|
||||
- name: Test
|
||||
run: go test -race ./...
|
||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
.idea/
|
||||
47
README.md
Normal file
47
README.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# dasel
|
||||
|
||||
Read and modify data structures using selectors.
|
||||
|
||||
## Selectors
|
||||
The following YAML data structure will be used as a reference in the following examples.
|
||||
```
|
||||
name: Tom
|
||||
preferences:
|
||||
favouriteColour: red
|
||||
colours:
|
||||
- red
|
||||
- green
|
||||
- blue
|
||||
colourCodes:
|
||||
- name: red
|
||||
rgb: ff0000
|
||||
- name: green
|
||||
rgb: 00ff00
|
||||
- name: blue
|
||||
rgb: 0000ff
|
||||
```
|
||||
|
||||
### Root Element
|
||||
Just use the root element name as a string.
|
||||
- `name` == `Tom`
|
||||
|
||||
### Child Element
|
||||
Just separate the parent element from the parent element using a `.`:
|
||||
- `preferences.favouriteColour` == `red`
|
||||
|
||||
#### Index
|
||||
When you have a list, you can use square brackets to access or modify a specific item.
|
||||
- `colours.[0]` == `red`
|
||||
- `colours.[1]` == `green`
|
||||
- `colours.[2]` == `blue`
|
||||
|
||||
#### Next Available Index
|
||||
Next available index selector is used when adding to a list of items.
|
||||
- `colours.[]`
|
||||
|
||||
#### Look up
|
||||
Look ups are defined in brackets and allow you to dynamically select an object to use.
|
||||
- `.colourCodes.(name=red).rgb` == `ff0000`
|
||||
- `.colourCodes.(name=green).rgb` == `00ff00`
|
||||
- `.colourCodes.(name=blue).rgb` == `0000ff`
|
||||
- `.colourCodes.(name=blue)(rgb=0000ff).rgb` == `0000ff`
|
||||
14
cmd/dasel/main.go
Normal file
14
cmd/dasel/main.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/tomwright/dasel/internal/command"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if err := command.RootCMD.Execute(); err != nil {
|
||||
fmt.Println("Error: " + err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
25
condition.go
Normal file
25
condition.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package dasel
|
||||
|
||||
import "fmt"
|
||||
|
||||
type EqualCondition struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (c EqualCondition) Check(other interface{}) (bool, error) {
|
||||
switch o := other.(type) {
|
||||
case map[string]string:
|
||||
return o[c.Key] == c.Value, nil
|
||||
case map[string]interface{}:
|
||||
return fmt.Sprint(o[c.Key]) == c.Value, nil
|
||||
case map[interface{}]interface{}:
|
||||
return fmt.Sprint(o[c.Key]) == c.Value, nil
|
||||
default:
|
||||
return false, fmt.Errorf("unhandled check type: %T", other)
|
||||
}
|
||||
}
|
||||
|
||||
type Condition interface {
|
||||
Check(other interface{}) (bool, error)
|
||||
}
|
||||
58
error.go
Normal file
58
error.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package dasel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var ErrMissingPreviousNode = errors.New("missing previous node")
|
||||
|
||||
type UnknownComparisonOperatorErr struct {
|
||||
Operator string
|
||||
}
|
||||
|
||||
func (e UnknownComparisonOperatorErr) Error() string {
|
||||
return fmt.Sprintf("unknown comparison operator: %s", e.Operator)
|
||||
}
|
||||
|
||||
type InvalidIndexErr struct {
|
||||
Index string
|
||||
}
|
||||
|
||||
func (e InvalidIndexErr) Error() string {
|
||||
return fmt.Sprintf("invalid index: %s", e.Index)
|
||||
}
|
||||
|
||||
type UnsupportedSelector struct {
|
||||
Selector string
|
||||
}
|
||||
|
||||
func (e UnsupportedSelector) Error() string {
|
||||
return fmt.Sprintf("selector is not supported here: %s", e.Selector)
|
||||
}
|
||||
|
||||
type UnsupportedTypeForSelector struct {
|
||||
Selector Selector
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
func (e UnsupportedTypeForSelector) Error() string {
|
||||
return fmt.Sprintf("selector [%s] does not support value: %T: %v", e.Selector.Type, e.Value, e.Value)
|
||||
}
|
||||
|
||||
type NotFound struct {
|
||||
Selector string
|
||||
Node *Node
|
||||
}
|
||||
|
||||
func (e NotFound) Error() string {
|
||||
return fmt.Sprintf("nothing found for selector: %s", e.Selector)
|
||||
}
|
||||
|
||||
type UnexpectedPreviousNilValue struct {
|
||||
Selector string
|
||||
}
|
||||
|
||||
func (e UnexpectedPreviousNilValue) Error() string {
|
||||
return fmt.Sprintf("previous value is nil: %s", e.Selector)
|
||||
}
|
||||
9
go.mod
Normal file
9
go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module github.com/tomwright/dasel
|
||||
|
||||
go 1.15
|
||||
|
||||
require (
|
||||
github.com/google/go-cmp v0.2.0
|
||||
github.com/spf13/cobra v1.0.0
|
||||
gopkg.in/yaml.v2 v2.2.2
|
||||
)
|
||||
129
go.sum
Normal file
129
go.sum
Normal file
@@ -0,0 +1,129 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
|
||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomwright/dasel v0.0.0-20200921150700-8299bb4a7d0e h1:GHe5Rd9wbkACk+fvtGQe9f2rBFL2jwrL0exkE9sgvsw=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
19
internal/command/root.go
Normal file
19
internal/command/root.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var RootCMD = &cobra.Command{
|
||||
Use: "dasel",
|
||||
Aliases: nil,
|
||||
SuggestFor: nil,
|
||||
Short: "A small helper to manage kubernetes configurations.",
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCMD.AddCommand(
|
||||
selectCommand(),
|
||||
versionCommand(),
|
||||
)
|
||||
}
|
||||
43
internal/command/select.go
Normal file
43
internal/command/select.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
dasel "github.com/tomwright/dasel"
|
||||
"github.com/tomwright/dasel/internal/storage"
|
||||
)
|
||||
|
||||
func selectCommand() *cobra.Command {
|
||||
var file, selector, parser string
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "select -f <file> -s <selector>",
|
||||
Short: "Select properties from the given files.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
parser, err := storage.FromString(parser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value, err := parser.LoadFromFile(file)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not load file: %w", err)
|
||||
}
|
||||
rootNode := dasel.New(value)
|
||||
res, err := rootNode.Query(selector)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not query node: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("%v\n", res.Value)
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&file, "file", "f", "", "The file to query.")
|
||||
cmd.Flags().StringVarP(&selector, "selector", "s", "", "The selector to use when querying.")
|
||||
cmd.Flags().StringVarP(&parser, "parser", "p", "yaml", "The parser to use with the given file.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
20
internal/command/version.go
Normal file
20
internal/command/version.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/tomwright/dasel/internal"
|
||||
)
|
||||
|
||||
func versionCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Prints the dasel version.",
|
||||
Args: cobra.ExactArgs(0),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fmt.Println(internal.Version)
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
111
internal/oflag/flags.go
Normal file
111
internal/oflag/flags.go
Normal file
@@ -0,0 +1,111 @@
|
||||
package oflag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Override represents a single override.
|
||||
type Override struct {
|
||||
// Path is a string that may have dot separators within it.
|
||||
Path string
|
||||
// Value is the value to set in the config file.
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
// String returns a string representation of the override.
|
||||
func (o Override) String() string {
|
||||
return fmt.Sprintf("%s=%v", o.Path, o.Value)
|
||||
}
|
||||
|
||||
// NewOverrideFlag returns an OverrideFlag with the given parse func.
|
||||
func NewOverrideFlag(parse ParseOverrideValueFn) *OverrideFlag {
|
||||
return &OverrideFlag{
|
||||
overrides: make([]*Override, 0),
|
||||
Parse: parse,
|
||||
}
|
||||
}
|
||||
|
||||
// Combine returns a combined list of overrides from all given OverrideFlag's.
|
||||
func Combine(flags ...*OverrideFlag) []*Override {
|
||||
res := make([]*Override, 0)
|
||||
for _, f := range flags {
|
||||
res = append(res, f.Overrides()...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// OverrideFlag allows us to collect a list of overrides from command line flags.
|
||||
type OverrideFlag struct {
|
||||
overrides []*Override
|
||||
// Parse allows us to parse override values into specific types.
|
||||
Parse ParseOverrideValueFn
|
||||
}
|
||||
|
||||
// Overrides returns the collected overrides.
|
||||
func (of *OverrideFlag) Overrides() []*Override {
|
||||
return of.overrides
|
||||
}
|
||||
|
||||
// Type returns the type of flag.
|
||||
// As far as I can tell this isn't used...
|
||||
// todo : figure out what this is supposed to return.
|
||||
func (of *OverrideFlag) Type() string {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
// String returns a string representation of all Override's within the OverrideFlag.
|
||||
func (of *OverrideFlag) String() string {
|
||||
val := make([]string, len(of.overrides))
|
||||
for k, o := range of.overrides {
|
||||
val[k] = o.String()
|
||||
}
|
||||
return strings.Join(val, " ")
|
||||
}
|
||||
|
||||
// Set is used to add a new value to the OverrideFlag.
|
||||
func (of *OverrideFlag) Set(value string) error {
|
||||
if value == "" {
|
||||
return nil
|
||||
}
|
||||
args := strings.Split(value, "=")
|
||||
|
||||
var override *Override
|
||||
switch len(args) {
|
||||
case 0:
|
||||
// No value was given.
|
||||
return nil
|
||||
case 1:
|
||||
// A blank value was given.
|
||||
override = &Override{
|
||||
Path: args[0],
|
||||
Value: "",
|
||||
}
|
||||
case 2:
|
||||
// A single value was given.
|
||||
override = &Override{
|
||||
Path: args[0],
|
||||
Value: args[1],
|
||||
}
|
||||
default:
|
||||
// The value contained more than 1 = sign.
|
||||
// Assume the extra = values are within the value.
|
||||
override = &Override{
|
||||
Path: args[0],
|
||||
Value: strings.Join(args[1:], "="),
|
||||
}
|
||||
}
|
||||
|
||||
if of.Parse != nil {
|
||||
var err error
|
||||
// We know that override.Value will be a string here.
|
||||
override.Value, err = of.Parse(override.Value.(string))
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid value for path `%s`: %w", override.Path, err)
|
||||
}
|
||||
}
|
||||
|
||||
of.overrides = append(of.overrides, override)
|
||||
|
||||
return nil
|
||||
}
|
||||
69
internal/oflag/flags_test.go
Normal file
69
internal/oflag/flags_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package oflag_test
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/tomwright/dasel/internal/oflag"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOverrideFlag_Set(t *testing.T) {
|
||||
overrides := oflag.NewOverrideFlag(oflag.StringParser)
|
||||
if err := overrides.Set("a=asd"); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if err := overrides.Set("b="); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if err := overrides.Set("c=a=b"); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if err := overrides.Set("a.b.c=d.e.f"); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
if err := overrides.Set(""); err != nil {
|
||||
t.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
exp := []*oflag.Override{
|
||||
{
|
||||
Path: "a",
|
||||
Value: "asd",
|
||||
},
|
||||
{
|
||||
Path: "b",
|
||||
Value: "",
|
||||
},
|
||||
{
|
||||
Path: "c",
|
||||
Value: "a=b",
|
||||
},
|
||||
{
|
||||
Path: "a.b.c",
|
||||
Value: "d.e.f",
|
||||
},
|
||||
}
|
||||
|
||||
got := overrides.Overrides()
|
||||
|
||||
if !cmp.Equal(exp, got) {
|
||||
t.Errorf("unexpected data:\n%s\n", cmp.Diff(exp, got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombine(t *testing.T) {
|
||||
a := oflag.NewOverrideFlag(nil)
|
||||
b := oflag.NewOverrideFlag(nil)
|
||||
c := oflag.NewOverrideFlag(nil)
|
||||
_ = a.Set("a=1")
|
||||
_ = b.Set("b=2")
|
||||
_ = c.Set("c=3")
|
||||
got := oflag.Combine(a, b, c)
|
||||
exp := []*oflag.Override{
|
||||
{Path: "a", Value: "1"},
|
||||
{Path: "b", Value: "2"},
|
||||
{Path: "c", Value: "3"},
|
||||
}
|
||||
if !cmp.Equal(exp, got) {
|
||||
t.Errorf("unexpected combined result:\n%s\n", cmp.Diff(exp, got))
|
||||
}
|
||||
}
|
||||
39
internal/oflag/parser.go
Normal file
39
internal/oflag/parser.go
Normal file
@@ -0,0 +1,39 @@
|
||||
package oflag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParseOverrideValueFn defines a function used to parse config values passed in through command line arguments.
|
||||
type ParseOverrideValueFn func(value string) (interface{}, error)
|
||||
|
||||
// StringParser parses string config values.
|
||||
func StringParser(value string) (interface{}, error) {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
// IntParser parses int config values.
|
||||
func IntParser(value string) (interface{}, error) {
|
||||
i, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return int(i), nil
|
||||
}
|
||||
|
||||
// ErrInvalidBool is returned if the given value does not match an expected bool value.
|
||||
var ErrInvalidBool = errors.New("unexpected bool value")
|
||||
|
||||
// BoolParser parses int config values.
|
||||
func BoolParser(value string) (interface{}, error) {
|
||||
switch strings.ToLower(value) {
|
||||
case "yes", "y", "true", "t", "1":
|
||||
return true, nil
|
||||
case "no", "n", "false", "f", "0":
|
||||
return false, nil
|
||||
default:
|
||||
return false, ErrInvalidBool
|
||||
}
|
||||
}
|
||||
92
internal/oflag/parser_test.go
Normal file
92
internal/oflag/parser_test.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package oflag_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/tomwright/dasel/internal/oflag"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type testCase struct {
|
||||
In string
|
||||
Exp interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
func runTestCase(tc testCase, fn oflag.ParseOverrideValueFn) func(t *testing.T) {
|
||||
return func(t *testing.T) {
|
||||
got, err := fn(tc.In)
|
||||
if tc.Exp == nil && got != nil {
|
||||
t.Errorf("unexpected error: %v", got)
|
||||
} else if tc.Exp != nil && got == nil {
|
||||
t.Errorf("expected error `%v`, got none", tc.Exp)
|
||||
} else if !errors.Is(err, tc.Err) {
|
||||
t.Errorf("expected error `%v`, got `%v`", tc.Err, err)
|
||||
}
|
||||
if !cmp.Equal(tc.Exp, got) {
|
||||
t.Errorf("unexpected output\n%s", cmp.Diff(tc.Exp, got))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStringParser(t *testing.T) {
|
||||
tests := []testCase{
|
||||
{In: "", Exp: "", Err: nil},
|
||||
{In: "a", Exp: "a", Err: nil},
|
||||
{In: "true", Exp: "true", Err: nil},
|
||||
{In: "123", Exp: "123", Err: nil},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
tc := testCase
|
||||
t.Run(tc.In, runTestCase(tc, oflag.StringParser))
|
||||
}
|
||||
}
|
||||
|
||||
func TestBoolParser(t *testing.T) {
|
||||
tests := []testCase{
|
||||
{In: "", Exp: false, Err: oflag.ErrInvalidBool},
|
||||
{In: "asd", Exp: false, Err: oflag.ErrInvalidBool},
|
||||
{In: "yes", Exp: true, Err: nil},
|
||||
{In: "YES", Exp: true, Err: nil},
|
||||
{In: "y", Exp: true, Err: nil},
|
||||
{In: "Y", Exp: true, Err: nil},
|
||||
{In: "true", Exp: true, Err: nil},
|
||||
{In: "TRUE", Exp: true, Err: nil},
|
||||
{In: "t", Exp: true, Err: nil},
|
||||
{In: "T", Exp: true, Err: nil},
|
||||
{In: "1", Exp: true, Err: nil},
|
||||
{In: "no", Exp: false, Err: nil},
|
||||
{In: "NO", Exp: false, Err: nil},
|
||||
{In: "n", Exp: false, Err: nil},
|
||||
{In: "N", Exp: false, Err: nil},
|
||||
{In: "false", Exp: false, Err: nil},
|
||||
{In: "FALSE", Exp: false, Err: nil},
|
||||
{In: "f", Exp: false, Err: nil},
|
||||
{In: "F", Exp: false, Err: nil},
|
||||
{In: "0", Exp: false, Err: nil},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
tc := testCase
|
||||
t.Run(tc.In, runTestCase(tc, oflag.BoolParser))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntParser(t *testing.T) {
|
||||
tests := []testCase{
|
||||
{In: "", Exp: 0, Err: strconv.ErrSyntax},
|
||||
{In: "asd", Exp: 0, Err: strconv.ErrSyntax},
|
||||
{In: "1.0", Exp: 0, Err: strconv.ErrSyntax},
|
||||
{In: "0", Exp: 0, Err: nil},
|
||||
{In: "12", Exp: 12, Err: nil},
|
||||
{In: "-1", Exp: -1, Err: nil},
|
||||
{In: "12345678", Exp: 12345678, Err: nil},
|
||||
}
|
||||
|
||||
for _, testCase := range tests {
|
||||
tc := testCase
|
||||
t.Run(tc.In, runTestCase(tc, oflag.IntParser))
|
||||
}
|
||||
}
|
||||
50
internal/storage/parser.go
Normal file
50
internal/storage/parser.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"gopkg.in/yaml.v2"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
type unknownParserErr struct {
|
||||
parser string
|
||||
}
|
||||
|
||||
func (e unknownParserErr) Error() string {
|
||||
return fmt.Sprintf("unknown parser: %s", e.parser)
|
||||
}
|
||||
|
||||
type Parser interface {
|
||||
FromBytes(byteData []byte) (interface{}, error)
|
||||
LoadFromFile(filename string) (interface{}, error)
|
||||
}
|
||||
|
||||
func FromString(parser string) (Parser, error) {
|
||||
switch parser {
|
||||
case "yaml":
|
||||
return &YAMLParser{}, nil
|
||||
default:
|
||||
return nil, &unknownParserErr{parser: parser}
|
||||
}
|
||||
}
|
||||
|
||||
type YAMLParser struct {
|
||||
}
|
||||
|
||||
// FromBytes returns some Data that is represented by the given bytes.
|
||||
func (p *YAMLParser) FromBytes(byteData []byte) (interface{}, error) {
|
||||
var data interface{}
|
||||
if err := yaml.Unmarshal(byteData, &data); err != nil {
|
||||
return data, fmt.Errorf("could not unmarshal config data: %w", err)
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// LoadFromFile loads Data from the given file.
|
||||
func (p *YAMLParser) LoadFromFile(filename string) (interface{}, error) {
|
||||
byteData, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config file: %w", err)
|
||||
}
|
||||
return p.FromBytes(byteData)
|
||||
}
|
||||
5
internal/version.go
Normal file
5
internal/version.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package internal
|
||||
|
||||
// Version represents the current version of dasel.
|
||||
// The real version number is injected at build time using ldflags.
|
||||
var Version = "development"
|
||||
346
node.go
Normal file
346
node.go
Normal file
@@ -0,0 +1,346 @@
|
||||
package dasel
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Selector represents the selector for a node.
|
||||
type Selector struct {
|
||||
// Raw is the full selector.
|
||||
Raw string `json:"raw"`
|
||||
// Current is the selector to be used with the current node.
|
||||
Current string `json:"current"`
|
||||
// Remaining is the remaining parts of the Raw selector.
|
||||
Remaining string `json:"remaining"`
|
||||
// Type is the type of the selector.
|
||||
Type string `json:"type"`
|
||||
// Property is the name of the property this selector targets, if applicable.
|
||||
Property string `json:"property,omitempty"`
|
||||
// Index is the index to use if applicable.
|
||||
Index int64 `json:"index,omitempty"`
|
||||
// Conditions contains a set of conditions to optionally match a target.
|
||||
Conditions []Condition `json:"conditions,omitempty"`
|
||||
}
|
||||
|
||||
// Node represents a single node in the chain of nodes for a selector.
|
||||
type Node struct {
|
||||
// Previous is the previous node in the chain.
|
||||
Previous *Node `json:"-"`
|
||||
// Next is the next node in the chain.
|
||||
Next *Node `json:"next,omitempty"`
|
||||
|
||||
// Value is the value of the current node.
|
||||
Value interface{} `json:"value"`
|
||||
// Selector is the selector for the current node.
|
||||
Selector Selector `json:"selector"`
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
b, err := json.MarshalIndent(n, "", " ")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
const (
|
||||
propertySelector = `(?P<property>[a-zA-Z\-_]+)`
|
||||
indexSelector = `\[(?P<index>[0-9]*?)\]`
|
||||
dynamicSelector = `\((?P<name>[a-zA-Z\-_]+)(?P<comparison>=)(?P<name>.*?)\)`
|
||||
)
|
||||
|
||||
var (
|
||||
// firstNodeRegexp = regexp.MustCompile(`(.+\.?)*?`)
|
||||
propertyRegexp = regexp.MustCompile(fmt.Sprintf("^\\.?%s", propertySelector))
|
||||
indexRegexp = regexp.MustCompile(fmt.Sprintf("^\\.?%s", indexSelector))
|
||||
dynamicRegexp = regexp.MustCompile(fmt.Sprintf("^\\.?(?:%s)+", dynamicSelector))
|
||||
multipleDynamicRegexp = regexp.MustCompile(fmt.Sprintf("%s+", dynamicSelector))
|
||||
)
|
||||
|
||||
// ParseSelector parses the given selector string and returns a Selector.
|
||||
func ParseSelector(selector string) (Selector, error) {
|
||||
sel := Selector{
|
||||
Raw: selector,
|
||||
Current: "",
|
||||
Remaining: "",
|
||||
Type: "",
|
||||
Property: "",
|
||||
Conditions: make([]Condition, 0),
|
||||
}
|
||||
|
||||
if match := propertyRegexp.FindStringSubmatch(selector); len(match) != 0 {
|
||||
sel.Type = "PROPERTY"
|
||||
sel.Current = match[0]
|
||||
sel.Property = match[1]
|
||||
} else if match := indexRegexp.FindStringSubmatch(selector); len(match) != 0 {
|
||||
sel.Current = match[0]
|
||||
if match[1] == "" {
|
||||
sel.Type = "NEXT_AVAILABLE_INDEX"
|
||||
} else {
|
||||
sel.Type = "INDEX"
|
||||
var err error
|
||||
sel.Index, err = strconv.ParseInt(match[1], 10, 64)
|
||||
if err != nil {
|
||||
return sel, &InvalidIndexErr{Index: match[1]}
|
||||
}
|
||||
}
|
||||
} else if match := dynamicRegexp.FindString(selector); match != "" {
|
||||
sel.Current = match
|
||||
matches := multipleDynamicRegexp.FindAllStringSubmatch(match, -1)
|
||||
for _, m := range matches {
|
||||
var cond Condition
|
||||
switch m[2] {
|
||||
case "=":
|
||||
cond = &EqualCondition{
|
||||
Key: m[1],
|
||||
Value: m[3],
|
||||
}
|
||||
default:
|
||||
return sel, &UnknownComparisonOperatorErr{Operator: m[2]}
|
||||
}
|
||||
|
||||
sel.Conditions = append(sel.Conditions, cond)
|
||||
}
|
||||
sel.Type = "DYNAMIC"
|
||||
}
|
||||
|
||||
sel.Remaining = strings.TrimPrefix(sel.Raw, sel.Current)
|
||||
|
||||
return sel, nil
|
||||
}
|
||||
|
||||
func New(value interface{}) *Node {
|
||||
rootNode := &Node{
|
||||
Previous: nil,
|
||||
Next: nil,
|
||||
Value: value,
|
||||
Selector: Selector{
|
||||
Raw: ".",
|
||||
Current: ".",
|
||||
Remaining: "",
|
||||
Type: "ROOT",
|
||||
Property: "",
|
||||
},
|
||||
}
|
||||
return rootNode
|
||||
}
|
||||
|
||||
func (n Node) Query(selector string) (*Node, error) {
|
||||
n.Selector.Remaining = selector
|
||||
rootNode := &n
|
||||
var previousNode = rootNode
|
||||
var nextNode *Node
|
||||
var err error
|
||||
|
||||
for {
|
||||
if previousNode == nil {
|
||||
break
|
||||
}
|
||||
if previousNode.Selector.Remaining == "" {
|
||||
break
|
||||
}
|
||||
|
||||
nextNode = &Node{}
|
||||
|
||||
// Parse the selector.
|
||||
nextNode.Selector, err = ParseSelector(previousNode.Selector.Remaining)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse selector: %w", err)
|
||||
}
|
||||
|
||||
// Link the nodes.
|
||||
previousNode.Next = nextNode
|
||||
nextNode.Previous = previousNode
|
||||
|
||||
nextNode.Value, err = FindValue(nextNode)
|
||||
// Populate the value for the new node.
|
||||
if err != nil {
|
||||
return nil, &NotFound{Selector: nextNode.Selector.Current, Node: nextNode}
|
||||
}
|
||||
|
||||
previousNode = nextNode
|
||||
}
|
||||
|
||||
return previousNode, nil
|
||||
}
|
||||
|
||||
// findValueProperty finds the value for the given node using the property selector
|
||||
// information.
|
||||
func findValueProperty(n *Node) (interface{}, error) {
|
||||
switch p := n.Previous.Value.(type) {
|
||||
case nil:
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: n.Previous.Selector.Current}
|
||||
case map[string]interface{}:
|
||||
v, ok := p[n.Selector.Property]
|
||||
if ok {
|
||||
return v, nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
v, ok := p[n.Selector.Property]
|
||||
if ok {
|
||||
return v, nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
default:
|
||||
return nil, &UnsupportedTypeForSelector{Selector: n.Selector, Value: n.Previous.Value}
|
||||
}
|
||||
}
|
||||
|
||||
// findValueIndex finds the value for the given node using the index selector
|
||||
// information.
|
||||
func findValueIndex(n *Node) (interface{}, error) {
|
||||
switch p := n.Previous.Value.(type) {
|
||||
case nil:
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: n.Previous.Selector.Current}
|
||||
case []map[interface{}]interface{}:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case []map[string]interface{}:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case map[interface{}]interface{}:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case map[int]interface{}:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[int(n.Selector.Index)], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case []interface{}:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case []string:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
case []int:
|
||||
l := int64(len(p))
|
||||
if n.Selector.Index >= 0 && n.Selector.Index < l {
|
||||
return p[n.Selector.Index], nil
|
||||
} else {
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
}
|
||||
default:
|
||||
return nil, &UnsupportedTypeForSelector{Selector: n.Selector, Value: n.Previous.Value}
|
||||
}
|
||||
}
|
||||
|
||||
// findValueDynamic finds the value for the given node using the dynamic selector
|
||||
// information.
|
||||
func findValueDynamic(n *Node) (interface{}, error) {
|
||||
switch p := n.Previous.Value.(type) {
|
||||
case nil:
|
||||
return nil, &UnexpectedPreviousNilValue{Selector: n.Previous.Selector.Current}
|
||||
case []map[interface{}]interface{}:
|
||||
for _, v := range p {
|
||||
// Loop through each condition.
|
||||
allConditionsMatched := true
|
||||
for _, c := range n.Selector.Conditions {
|
||||
// If the object doesn't match any checks, return a NotFound.
|
||||
found, err := c.Check(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
allConditionsMatched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allConditionsMatched {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
case []map[string]interface{}:
|
||||
for _, v := range p {
|
||||
// Loop through each condition.
|
||||
allConditionsMatched := true
|
||||
for _, c := range n.Selector.Conditions {
|
||||
// If the object doesn't match any checks, return a NotFound.
|
||||
found, err := c.Check(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
allConditionsMatched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allConditionsMatched {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
case []map[string]string:
|
||||
for _, v := range p {
|
||||
// Loop through each condition.
|
||||
allConditionsMatched := true
|
||||
for _, c := range n.Selector.Conditions {
|
||||
// If the object doesn't match any checks, return a NotFound.
|
||||
found, err := c.Check(v)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !found {
|
||||
allConditionsMatched = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allConditionsMatched {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return nil, &NotFound{Selector: n.Selector.Current, Node: n}
|
||||
default:
|
||||
return nil, &UnsupportedTypeForSelector{Selector: n.Selector, Value: n.Previous.Value}
|
||||
}
|
||||
}
|
||||
|
||||
// FindValue finds the value for the given node.
|
||||
// The value is essentially pulled from the previous node, using the (already parsed) selector
|
||||
// information stored on the current node.
|
||||
func FindValue(n *Node) (interface{}, error) {
|
||||
if n.Previous == nil {
|
||||
// previous node is required to get it's value.
|
||||
return nil, ErrMissingPreviousNode
|
||||
}
|
||||
|
||||
switch n.Selector.Type {
|
||||
case "PROPERTY":
|
||||
return findValueProperty(n)
|
||||
case "INDEX":
|
||||
return findValueIndex(n)
|
||||
case "DYNAMIC":
|
||||
return findValueDynamic(n)
|
||||
default:
|
||||
return nil, &UnsupportedSelector{Selector: n.Selector.Type}
|
||||
}
|
||||
}
|
||||
254
node_test.go
Normal file
254
node_test.go
Normal file
@@ -0,0 +1,254 @@
|
||||
package dasel_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/tomwright/dasel"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
tom = map[string]interface{}{
|
||||
"name": "Tom",
|
||||
"age": 28,
|
||||
}
|
||||
amelia = map[string]interface{}{
|
||||
"name": "Amelia",
|
||||
"age": 26,
|
||||
}
|
||||
people = []map[string]interface{}{tom, amelia}
|
||||
mapC = map[string]interface{}{
|
||||
"thing": "1",
|
||||
}
|
||||
mapB = map[string]interface{}{
|
||||
"c": mapC,
|
||||
"people": people,
|
||||
}
|
||||
mapA = map[string]interface{}{
|
||||
"b": mapB,
|
||||
}
|
||||
mapRoot = map[string]interface{}{
|
||||
"a": mapA,
|
||||
}
|
||||
)
|
||||
|
||||
type processFn func(t *testing.T, n *dasel.Node) (*dasel.Node, bool)
|
||||
type checkFn func(t *testing.T, n *dasel.Node) bool
|
||||
|
||||
type parseTest struct {
|
||||
Selector string
|
||||
Input interface{}
|
||||
ProcessFns []processFn
|
||||
CheckFns []checkFn
|
||||
Names []string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (pt parseTest) Add(name string, p processFn, c checkFn) parseTest {
|
||||
if pt.ProcessFns == nil {
|
||||
pt.ProcessFns = make([]processFn, 0)
|
||||
}
|
||||
if pt.CheckFns == nil {
|
||||
pt.CheckFns = make([]checkFn, 0)
|
||||
}
|
||||
if pt.Names == nil {
|
||||
pt.Names = make([]string, 0)
|
||||
}
|
||||
pt.Names = append(pt.Names, name)
|
||||
pt.ProcessFns = append(pt.ProcessFns, p)
|
||||
pt.CheckFns = append(pt.CheckFns, c)
|
||||
return pt
|
||||
}
|
||||
|
||||
func TestNode_Query(t *testing.T) {
|
||||
t.Run("ParentChildPathToProperty", func(t *testing.T) {
|
||||
rootNode := dasel.New(mapRoot)
|
||||
|
||||
got, err := rootNode.Query(".a.b.c.thing")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if exp, got := "1", got.Value.(string); exp != got {
|
||||
t.Errorf("expected %s, got %s", exp, got)
|
||||
}
|
||||
})
|
||||
t.Run("ParentChildPathToIndexProperty", func(t *testing.T) {
|
||||
rootNode := dasel.New(mapRoot)
|
||||
|
||||
got, err := rootNode.Query(".a.b.people.[1].name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if exp, got := "Amelia", got.Value.(string); exp != got {
|
||||
t.Errorf("expected %s, got %s", exp, got)
|
||||
}
|
||||
})
|
||||
t.Run("ParentChildPathToDynamicProperty", func(t *testing.T) {
|
||||
rootNode := dasel.New(mapRoot)
|
||||
|
||||
got, err := rootNode.Query(".a.b.people.(name=Tom).name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if exp, got := "Tom", got.Value.(string); exp != got {
|
||||
t.Errorf("expected %s, got %s", exp, got)
|
||||
}
|
||||
})
|
||||
t.Run("ParentChildPathToMultipleDynamicProperty", func(t *testing.T) {
|
||||
rootNode := dasel.New(mapRoot)
|
||||
|
||||
got, err := rootNode.Query(".a.b.people.(name=Tom)(age=28).name")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
if exp, got := "Tom", got.Value.(string); exp != got {
|
||||
t.Errorf("expected %s, got %s", exp, got)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Traversal", func(t *testing.T) {
|
||||
tests := parseTest{
|
||||
Selector: ".a.b.c.thing",
|
||||
Input: mapRoot,
|
||||
}.Add(
|
||||
"StartsAtEndElement",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".thing", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual("1", n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", "1", n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Previous1",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Previous, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".c", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(mapC, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapC, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Previous2",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Previous, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".b", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(mapB, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapB, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Previous3",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Previous, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".a", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(mapA, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapA, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Next",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Next, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".b", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(mapB, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapB, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Previous4",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Previous, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".a", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(mapA, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapA, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
).Add(
|
||||
"Previous5",
|
||||
func(t *testing.T, n *dasel.Node) (*dasel.Node, bool) { return n.Previous, true },
|
||||
func(t *testing.T, n *dasel.Node) bool {
|
||||
if exp, got := ".", n.Selector.Current; exp != got {
|
||||
t.Errorf("expected selector of `%s`, got `%s`", exp, got)
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(mapRoot, n.Value) {
|
||||
t.Errorf("expected value of\n%s\ngot\n%s", mapRoot, n.Value)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
)
|
||||
|
||||
rootNode := dasel.New(tests.Input)
|
||||
got, err := rootNode.Query(tests.Selector)
|
||||
switch {
|
||||
case err == nil && tests.Err != nil:
|
||||
t.Errorf("expected err `%v`, got `%v`", tests.Err, err)
|
||||
return
|
||||
case err != nil && tests.Err == nil:
|
||||
t.Errorf("unexpected err `%v`", err)
|
||||
return
|
||||
case err != nil && tests.Err != nil && !errors.Is(err, tests.Err):
|
||||
t.Errorf("expected err `%v`, got `%v`", tests.Err, err)
|
||||
return
|
||||
}
|
||||
|
||||
ok := true
|
||||
|
||||
for i := 0; i < len(tests.ProcessFns); i++ {
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
t.Run(tests.Names[i], func(t *testing.T) {
|
||||
got, ok = tests.ProcessFns[i](t, got)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ok = tests.CheckFns[i](t, got)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
14
tests/assets/example.yaml
Normal file
14
tests/assets/example.yaml
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Tom
|
||||
preferences:
|
||||
favouriteColour: red
|
||||
colours:
|
||||
- red
|
||||
- green
|
||||
- blue
|
||||
colourCodes:
|
||||
- name: red
|
||||
rgb: ff0000
|
||||
- name: green
|
||||
rgb: 00ff00
|
||||
- name: blue
|
||||
rgb: 0000ff
|
||||
Reference in New Issue
Block a user