diff options
author | Sergey Grebenshchikov <sgreben@gmail.com> | 2018-03-22 02:15:42 +0100 |
---|---|---|
committer | Sergey Grebenshchikov <sgreben@gmail.com> | 2018-03-22 10:04:51 +0100 |
commit | a49891bdb657d7cbb2521f305251cda1f00359d3 (patch) | |
tree | bb438378e68025641c54d0b2f2dfdcc73c33a4b0 /pkg |
Initial commit1.0.0
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/jp/barchart.go | 124 | ||||
-rw-r--r-- | pkg/jp/datatable.go | 14 | ||||
-rw-r--r-- | pkg/jp/linechart.go | 205 | ||||
-rw-r--r-- | pkg/jp/primitives/box.go | 6 | ||||
-rw-r--r-- | pkg/jp/primitives/buffer.go | 9 | ||||
-rw-r--r-- | pkg/jp/primitives/format.go | 8 | ||||
-rw-r--r-- | pkg/jp/primitives/line.go | 68 | ||||
-rw-r--r-- | pkg/jp/primitives/runes.go | 18 | ||||
-rw-r--r-- | pkg/jsonpath/LICENSE | 202 | ||||
-rw-r--r-- | pkg/jsonpath/funcs.go | 191 | ||||
-rw-r--r-- | pkg/jsonpath/jsonpath.go | 485 | ||||
-rw-r--r-- | pkg/jsonpath/node.go | 257 | ||||
-rw-r--r-- | pkg/jsonpath/parser.go | 541 | ||||
-rw-r--r-- | pkg/terminal/terminal.go | 46 | ||||
-rw-r--r-- | pkg/terminal/terminal_nosysioctl.go | 7 | ||||
-rw-r--r-- | pkg/terminal/terminal_sysioctl.go | 19 |
16 files changed, 2200 insertions, 0 deletions
diff --git a/pkg/jp/barchart.go b/pkg/jp/barchart.go new file mode 100644 index 0000000..c561f98 --- /dev/null +++ b/pkg/jp/barchart.go @@ -0,0 +1,124 @@ +package jp + +import ( + "math" + "strings" + + "github.com/sgreben/jp/pkg/jp/primitives" +) + +// BarChart is a bar chart +type BarChart struct { + Buffer []string + + data *DataTable + + Width int + Height int + Symbol string + BarPaddingX int + + chartHeight int + chartWidth int + + paddingX int + paddingY int +} + +// NewBarChart returns a new bar chart +func NewBarChart(width, height int) *BarChart { + chart := new(BarChart) + chart.Width = width + chart.Height = height + chart.Buffer = primitives.Buffer(width * height) + chart.Symbol = primitives.FullBlock4 + chart.paddingY = 3 + chart.BarPaddingX = 1 + return chart +} + +func (c *BarChart) writeText(text string, x, y int) { + coord := y*c.Width + x + + for idx, char := range strings.Split(text, "") { + if coord+idx >= 0 && coord+idx < len(c.Buffer) { + c.Buffer[coord+idx] = char + } + } +} + +// Draw implements Chart +func (c *BarChart) Draw(data *DataTable) (out string) { + + c.data = data + + minY := math.Inf(1) + maxY := math.Inf(-1) + for _, row := range data.Rows { + for _, y := range row { + if y < minY { + minY = y + } + if y > maxY { + maxY = y + } + } + } + + c.paddingX = 1 + c.chartHeight = c.Height - c.paddingY + c.chartWidth = c.Width - c.paddingX - 1 + scaleY := float64(c.chartHeight) / maxY + barPaddedWidth := c.chartWidth / len(data.Columns) + barWidth := barPaddedWidth - c.BarPaddingX + if barPaddedWidth < 1 { + barPaddedWidth = 1 + } + if barWidth < 1 { + barWidth = 1 + } + + scaleY = float64(c.chartHeight) / maxY + + for i, group := range data.Columns { + barLeft := c.paddingX + barPaddedWidth*i + barRight := barLeft + barWidth + y := data.Rows[0][i] + barHeight := y * scaleY + barTop := c.paddingY - 1 + int(barHeight) + for x := barLeft; x < barRight; x++ { + for y := c.paddingY - 1; y < barTop; y++ { + c.set(x, y, c.Symbol) + } + if barHeight < 1 && y > 0 { + for x := barLeft; x < barRight; x++ { + c.set(x, barTop, "▁") + } + } + } + + // Group label + barMiddle := (barLeft + barRight) / 2 + c.writeText(group, barMiddle-(len(group)/2), 0) + + // Count label + countLabelY := barTop + if barHeight < 1 { + countLabelY = barTop + 1 + } + c.writeText(primitives.Ff(y), barMiddle-(len(primitives.Ff(y))/2), countLabelY) + } + + for row := c.Height - 1; row >= 0; row-- { + out += strings.Join(c.Buffer[row*c.Width:(row+1)*c.Width], "") + "\n" + } + + return +} + +func (c *BarChart) set(x, y int, s string) { + coord := y*c.Width + x + if coord > 0 && coord < len(c.Buffer) { + c.Buffer[coord] = s + } +} diff --git a/pkg/jp/datatable.go b/pkg/jp/datatable.go new file mode 100644 index 0000000..f1a682e --- /dev/null +++ b/pkg/jp/datatable.go @@ -0,0 +1,14 @@ +package jp + +type DataTable struct { + Columns []string + Rows [][]float64 +} + +func (d *DataTable) AddColumn(name string) { + d.Columns = append(d.Columns, name) +} + +func (d *DataTable) AddRow(elms ...float64) { + d.Rows = append(d.Rows, elms) +} diff --git a/pkg/jp/linechart.go b/pkg/jp/linechart.go new file mode 100644 index 0000000..1849a1e --- /dev/null +++ b/pkg/jp/linechart.go @@ -0,0 +1,205 @@ +package jp + +import ( + "math" + "strings" + + "github.com/sgreben/jp/pkg/jp/primitives" +) + +// Adapted from https://github.com/buger/goterm under the MIT License. + +/* +MIT License + +Copyright (c) 2016 Leonid Bugaev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// LineChart is a line chart +type LineChart struct { + Buffer []string + Width int + Height int + Symbol string + chartHeight int + chartWidth int + paddingX int + paddingY int + + data *DataTable +} + +// NewLineChart returns a new line chart +func NewLineChart(width, height int) *LineChart { + chart := new(LineChart) + chart.Width = width + chart.Height = height + chart.Buffer = primitives.Buffer(width * height) + chart.Symbol = primitives.PointSymbolDefault + chart.paddingY = 2 + return chart +} + +func (c *LineChart) drawAxes(maxX, minX, maxY, minY float64, index int) { + c.drawLine(c.paddingX-1, 1, c.Width-1, 1, primitives.HorizontalLine) + c.drawLine(c.paddingX-1, 1, c.paddingX-1, c.Height-1, primitives.VerticalLine) + c.set(c.paddingX-1, c.paddingY-1, primitives.CornerBottomLeft) + + left := 0 // c.Width - c.paddingX + 1 + c.writeText(primitives.Ff(minY), left, 1) + + c.writeText(primitives.Ff(maxY), left, c.Height-1) + + c.writeText(primitives.Ff(minX), c.paddingX, 0) + + xCol := c.data.Columns[0] + c.writeText(c.data.Columns[0], c.Width/2-len(xCol)/2, 1) + + if len(c.data.Columns) < 3 { + col := c.data.Columns[index] + + for idx, char := range strings.Split(col, "") { + startFrom := c.Height/2 + len(col)/2 - idx + + c.writeText(char, c.paddingX-1, startFrom) + } + } + + c.writeText(primitives.Ff(maxX), c.Width-len(primitives.Ff(maxX)), 0) + +} + +func (c *LineChart) writeText(text string, x, y int) { + coord := y*c.Width + x + + for idx, char := range strings.Split(text, "") { + c.Buffer[coord+idx] = char + } +} + +// Draw implements Chart +func (c *LineChart) Draw(data *DataTable) (out string) { + var scaleY, scaleX float64 + + c.data = data + + charts := len(data.Columns) - 1 + + prevPoint := [2]int{-1, -1} + + maxX, minX, maxY, minY := getBoundaryValues(data, -1) + + c.paddingX = int(math.Max(float64(len(primitives.Ff(minY))), float64(len(primitives.Ff(maxY))))) + 1 + + c.chartHeight = c.Height - c.paddingY + c.chartWidth = c.Width - c.paddingX - 1 + + scaleX = float64(c.chartWidth) / (maxX - minX) + + scaleY = float64(c.chartHeight) / (maxY - minY) + + for i := 1; i < charts+1; i++ { + symbol := c.Symbol + + chartData := getChartData(data, i) + + for _, point := range chartData { + x := int((point[0]-minX)*scaleX) + c.paddingX + y := int((point[1])*scaleY) + c.paddingY + y = int((point[1]-minY)*scaleY) + c.paddingY + + if prevPoint[0] == -1 { + prevPoint[0] = x + prevPoint[1] = y + } + + if prevPoint[0] <= x { + c.drawLine(prevPoint[0], prevPoint[1], x, y, symbol) + } + + prevPoint[0] = x + prevPoint[1] = y + } + + c.drawAxes(maxX, minX, maxY, minY, i) + } + + for row := c.Height - 1; row >= 0; row-- { + out += strings.Join(c.Buffer[row*c.Width:(row+1)*c.Width], "") + "\n" + } + + return +} + +func (c *LineChart) set(x, y int, s string) { + coord := y*c.Width + x + if coord > 0 && coord < len(c.Buffer) { + c.Buffer[coord] = s + } +} + +func (c *LineChart) drawLine(x0, y0, x1, y1 int, symbol string) { + primitives.DrawLine(x0, y0, x1, y1, func(x, y int) { c.set(x, y, symbol) }) +} + +func getBoundaryValues(data *DataTable, index int) (maxX, minX, maxY, minY float64) { + maxX = math.Inf(-1) + minX = math.Inf(1) + maxY = math.Inf(-1) + minY = math.Inf(1) + + for _, r := range data.Rows { + maxX = math.Max(maxX, r[0]) + minX = math.Min(minX, r[0]) + + for idx, c := range r { + if idx > 0 { + if index == -1 || index == idx { + maxY = math.Max(maxY, c) + minY = math.Min(minY, c) + } + } + } + } + + if maxY > 0 { + maxY = maxY * 1.1 + } else { + maxY = maxY * 0.9 + } + + if minY > 0 { + minY = minY * 0.9 + } else { + minY = minY * 1.1 + } + + return +} + +func getChartData(data *DataTable, index int) (out [][]float64) { + for _, r := range data.Rows { + out = append(out, []float64{r[0], r[index]}) + } + + return +} diff --git a/pkg/jp/primitives/box.go b/pkg/jp/primitives/box.go new file mode 100644 index 0000000..38474c6 --- /dev/null +++ b/pkg/jp/primitives/box.go @@ -0,0 +1,6 @@ +package primitives + +type Box struct { + Width int + Height int +} diff --git a/pkg/jp/primitives/buffer.go b/pkg/jp/primitives/buffer.go new file mode 100644 index 0000000..4102322 --- /dev/null +++ b/pkg/jp/primitives/buffer.go @@ -0,0 +1,9 @@ +package primitives + +func Buffer(size int) (out []string) { + out = make([]string, size) + for i := range out { + out[i] = " " + } + return +} diff --git a/pkg/jp/primitives/format.go b/pkg/jp/primitives/format.go new file mode 100644 index 0000000..26df0e3 --- /dev/null +++ b/pkg/jp/primitives/format.go @@ -0,0 +1,8 @@ +package primitives + +import "fmt" + +// Format float +func Ff(num interface{}) string { + return fmt.Sprintf("%.1f", num) +} diff --git a/pkg/jp/primitives/line.go b/pkg/jp/primitives/line.go new file mode 100644 index 0000000..4bcedca --- /dev/null +++ b/pkg/jp/primitives/line.go @@ -0,0 +1,68 @@ +package primitives + +// Adapted from https://github.com/buger/goterm under the MIT License. + +/* +MIT License + +Copyright (c) 2016 Leonid Bugaev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +*/ + +// http://en.wikipedia.org/wiki/Bresenham's_line_algorithm +func DrawLine(x0, y0, x1, y1 int, plot func(int, int)) { + dx := x1 - x0 + if dx < 0 { + dx = -dx + } + dy := y1 - y0 + if dy < 0 { + dy = -dy + } + var sx, sy int + if x0 < x1 { + sx = 1 + } else { + sx = -1 + } + if y0 < y1 { + sy = 1 + } else { + sy = -1 + } + err := dx - dy + + for { + plot(x0, y0) + if x0 == x1 && y0 == y1 { + break + } + e2 := 2 * err + if e2 > -dy { + err -= dy + x0 += sx + } + if e2 < dx { + err += dx + y0 += sy + } + } +} diff --git a/pkg/jp/primitives/runes.go b/pkg/jp/primitives/runes.go new file mode 100644 index 0000000..03c0845 --- /dev/null +++ b/pkg/jp/primitives/runes.go @@ -0,0 +1,18 @@ +package primitives + +// Block elements +const ( + FullBlock1 = "░" + FullBlock2 = "▒" + FullBlock3 = "▓" + FullBlock4 = "█" +) + +// Box drawing characters +var ( + HorizontalLine = "─" + VerticalLine = "│" + CornerBottomLeft = "└" + PointSymbolDefault = "•" + Cross = "╳" +) diff --git a/pkg/jsonpath/LICENSE b/pkg/jsonpath/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/pkg/jsonpath/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/pkg/jsonpath/funcs.go b/pkg/jsonpath/funcs.go new file mode 100644 index 0000000..b745a33 --- /dev/null +++ b/pkg/jsonpath/funcs.go @@ -0,0 +1,191 @@ +package jsonpath + +// This file is copied from the Kubernetes jsonpath implementation and stripped of unused parts. + +//This file is copied from Go library text/template. +//The original private functions eq, ge, gt, le, lt, and ne +//are exported as public functions. + +import ( + "errors" + "reflect" +) + +var Equal = eq +var GreaterEqual = ge +var Greater = gt +var LessEqual = le +var Less = lt +var NotEqual = ne + +// Comparison. + +// TODO: Perhaps allow comparison between signed and unsigned integers. + +var ( + errBadComparisonType = errors.New("invalid type for comparison") + errBadComparison = errors.New("incompatible types for comparison") + errNoComparison = errors.New("missing argument for comparison") +) + +type kind int + +const ( + invalidKind kind = iota + boolKind + complexKind + intKind + floatKind + integerKind + stringKind + uintKind +) + +func basicKind(v reflect.Value) (kind, error) { + switch v.Kind() { + case reflect.Bool: + return boolKind, nil + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return intKind, nil + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return uintKind, nil + case reflect.Float32, reflect.Float64: + return floatKind, nil + case reflect.Complex64, reflect.Complex128: + return complexKind, nil + case reflect.String: + return stringKind, nil + } + return invalidKind, errBadComparisonType +} + +// eq evaluates the comparison a == b || a == c || ... +func eq(arg1 interface{}, arg2 ...interface{}) (bool, error) { + v1 := reflect.ValueOf(arg1) + k1, err := basicKind(v1) + if err != nil { + return false, err + } + if len(arg2) == 0 { + return false, errNoComparison + } + for _, arg := range arg2 { + v2 := reflect.ValueOf(arg) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + truth := false + if k1 != k2 { + // Special case: Can compare integer values regardless of type's sign. + switch { + case k1 == intKind && k2 == uintKind: + truth = v1.Int() >= 0 && uint64(v1.Int()) == v2.Uint() + case k1 == uintKind && k2 == intKind: + truth = v2.Int() >= 0 && v1.Uint() == uint64(v2.Int()) + default: + return false, errBadComparison + } + } else { + switch k1 { + case boolKind: + truth = v1.Bool() == v2.Bool() + case complexKind: + truth = v1.Complex() == v2.Complex() + case floatKind: + truth = v1.Float() == v2.Float() + case intKind: + truth = v1.Int() == v2.Int() + case stringKind: + truth = v1.String() == v2.String() + case uintKind: + truth = v1.Uint() == v2.Uint() + default: + panic("invalid kind") + } + } + if truth { + return true, nil + } + } + return false, nil +} + +// ne evaluates the comparison a != b. +func ne(arg1, arg2 interface{}) (bool, error) { + // != is the inverse of ==. + equal, err := eq(arg1, arg2) + return !equal, err +} + +// lt evaluates the comparison a < b. +func lt(arg1, arg2 interface{}) (bool, error) { + v1 := reflect.ValueOf(arg1) + k1, err := basicKind(v1) + if err != nil { + return false, err + } + v2 := reflect.ValueOf(arg2) + k2, err := basicKind(v2) + if err != nil { + return false, err + } + truth := false + if k1 != k2 { + // Special case: Can compare integer values regardless of type's sign. + switch { + case k1 == intKind && k2 == uintKind: + truth = v1.Int() < 0 || uint64(v1.Int()) < v2.Uint() + case k1 == uintKind && k2 == intKind: + truth = v2.Int() >= 0 && v1.Uint() < uint64(v2.Int()) + default: + return false, errBadComparison + } + } else { + switch k1 { + case boolKind, complexKind: + return false, errBadComparisonType + case floatKind: + truth = v1.Float() < v2.Float() + case intKind: + truth = v1.Int() < v2.Int() + case stringKind: + truth = v1.String() < v2.String() + case uintKind: + truth = v1.Uint() < v2.Uint() + default: + panic("invalid kind") + } + } + return truth, nil +} + +// le evaluates the comparison <= b. +func le(arg1, arg2 interface{}) (bool, error) { + // <= is < or ==. + lessThan, err := lt(arg1, arg2) + if lessThan || err != nil { + return lessThan, err + } + return eq(arg1, arg2) +} + +// gt evaluates the comparison a > b. +func gt(arg1, arg2 interface{}) (bool, error) { + // > is the inverse of <=. + lessOrEqual, err := le(arg1, arg2) + if err != nil { + return false, err + } + return !lessOrEqual, nil +} + +// ge evaluates the comparison a >= b. +func ge(arg1, arg2 interface{}) (bool, error) { + // >= is the inverse of <. + lessThan, err := lt(arg1, arg2) + if err != nil { + return false, err + } + return !lessThan, nil +} diff --git a/pkg/jsonpath/jsonpath.go b/pkg/jsonpath/jsonpath.go new file mode 100644 index 0000000..7ed9b16 --- /dev/null +++ b/pkg/jsonpath/jsonpath.go @@ -0,0 +1,485 @@ +// This file is copied from the Kubernetes jsonpath implementation and stripped of unused parts. + +/* +Copyright 2015 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package jsonpath + +import ( + "fmt" + "reflect" + "strings" +) + +type JSONPath struct { + name string + parser *Parser + stack [][]reflect.Value // push and pop values in different scopes + cur []reflect.Value // current scope values + beginRange int + inRange int + endRange int + + allowMissingKeys bool +} + +// New creates a new JSONPath with the given name. +func New(name string) *JSONPath { + return &JSONPath{ + name: name, + beginRange: 0, + inRange: 0, + endRange: 0, + } +} + +// AllowMissingKeys allows a caller to specify whether they want an error if a field or map key +// cannot be located, or simply an empty result. The receiver is returned for chaining. +func (j *JSONPath) AllowMissingKeys(allow bool) *JSONPath { + j.allowMissingKeys = allow + return j +} + +// Parse parses the given template and returns an error. +func (j *JSONPath) Parse(text string) error { + var err error + j.parser, err = Parse(j.name, text) + return err +} + +func (j *JSONPath) FindResults(data interface{}) ([][]reflect.Value, error) { + if j.parser == nil { + return nil, fmt.Errorf("%s is an incomplete jsonpath template", j.name) + } + + j.cur = []reflect.Value{reflect.ValueOf(data)} + nodes := j.parser.Root.Nodes + fullResult := [][]reflect.Value{} + for i := 0; i < len(nodes); i++ { + node := nodes[i] + results, err := j.walk(j.cur, node) + if err != nil { + return nil, err + } + + // encounter an end node, break the current block + if j.endRange > 0 && j.endRange <= j.inRange { + j.endRange -= 1 + break + } + // encounter a range node, start a range loop + if j.beginRange > 0 { + |