1
0
mirror of https://github.com/TomWright/dasel.git synced 2022-05-22 02:32:45 +03:00

Initial commit

This commit is contained in:
Tom Wright
2020-09-22 12:26:37 +01:00
commit 1529dcad15
21 changed files with 1411 additions and 0 deletions

42
.github/workflows/build.yaml vendored Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
.idea/

47
README.md Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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(),
)
}

View 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
}

View 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
View 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
}

View 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
View 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
}
}

View 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))
}
}

View 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
View 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
View 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
View 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
View 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