mirror of
https://github.com/jesseduffield/lazydocker.git
synced 2026-07-25 08:31:03 +00:00
feat: add FormatBigMetric, SetObjectFieldByPath from utils to support metric render
This commit is contained in:
parent
e758c35e3b
commit
96734cb3ef
2 changed files with 235 additions and 0 deletions
|
|
@ -410,3 +410,71 @@ func marshalIntoFormat(data interface{}, format string) ([]byte, error) {
|
|||
return nil, errors.New(fmt.Sprintf("Unsupported detailization format: %s", format))
|
||||
}
|
||||
}
|
||||
|
||||
// FormatBigMetric takes a big metric and formats it into a human readable string with the appropriate unit.
|
||||
// For example, if you give it 1000000 with a base unit of bytes, it will return "1.000 MB".
|
||||
// If you give it 1000000000 with a base unit of nanoseconds, it will return "1.000 s"
|
||||
func FormatBigMetric(number int64, baseUnitName string) string {
|
||||
memoryUnits := []string{"KB", "MB", "GB", "TB"}
|
||||
cpuUnits := []string{"µs", "ms", "s", "m", "h"}
|
||||
const unit = 1000
|
||||
if number < unit {
|
||||
return fmt.Sprintf("%d", number)
|
||||
}
|
||||
div, exp := int64(unit), 0
|
||||
for n := number / unit; n >= unit; n /= unit {
|
||||
div *= unit
|
||||
exp++
|
||||
}
|
||||
switch baseUnitName {
|
||||
case "bytes":
|
||||
return fmt.Sprintf("%.3f %s", float64(number)/float64(div), memoryUnits[exp-1])
|
||||
case "nanoseconds":
|
||||
return fmt.Sprintf("%.3f %s", float64(number)/float64(div), cpuUnits[exp-1])
|
||||
default:
|
||||
return fmt.Sprintf("%d", number)
|
||||
}
|
||||
}
|
||||
|
||||
// SetObjectFieldByPath takes an object, a path to a field in that object, and a value,
|
||||
// and sets the field at that path to the value.
|
||||
// For example, if you have an object {"a": {"b": {"c": 1}}}, a path of "a.b.c", and a value of 2,
|
||||
// it will set the object to {"a": {"b": {"c": 2}}}
|
||||
func SetObjectFieldByPath(object *map[string]interface{}, path string, value interface{}) error {
|
||||
re := regexp.MustCompile(`^\.`)
|
||||
if !re.MatchString(path) && path != "" {
|
||||
return fmt.Errorf("Invalid path format %s, path should start with a dot", path)
|
||||
}
|
||||
|
||||
targetObject := map[string]interface{}{}
|
||||
currentObject := *object
|
||||
targetObject = copyMapByPath(currentObject, targetObject, path, value)
|
||||
*object = targetObject
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyMapByPath is a helper function for SetObjectFieldByPath
|
||||
// that recursively copies an object while setting the value at the right path
|
||||
func copyMapByPath(currentObject map[string]interface{}, targetObject map[string]interface{}, path string, value interface{}) map[string]interface{} {
|
||||
if path == "" {
|
||||
return currentObject
|
||||
}
|
||||
keyPathSlice := strings.Split(path, ".")[1:]
|
||||
currentKeys := make([]string, 0, len(currentObject))
|
||||
for k := range currentObject {
|
||||
currentKeys = append(currentKeys, k)
|
||||
}
|
||||
for _, key := range currentKeys {
|
||||
if key != keyPathSlice[0] {
|
||||
targetObject[key] = currentObject[key]
|
||||
} else {
|
||||
if len(keyPathSlice) == 1 {
|
||||
targetObject[key] = value
|
||||
} else {
|
||||
nextObject := currentObject[key].(map[string]interface{})
|
||||
targetObject[key] = copyMapByPath(nextObject, map[string]interface{}{}, "."+strings.Join(keyPathSlice[1:], "."), value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return targetObject
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,3 +319,170 @@ quux:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetObjectFieldByPath(t *testing.T) {
|
||||
type scenario struct {
|
||||
object *map[string]interface{}
|
||||
path string
|
||||
value interface{}
|
||||
expected map[string]interface{}
|
||||
expectedErr error
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": "replacement",
|
||||
},
|
||||
path: ".foo",
|
||||
value: "bar",
|
||||
expected: map[string]interface{}{"foo": "bar"},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "replacement",
|
||||
},
|
||||
},
|
||||
path: ".foo.bar",
|
||||
value: "bar_1",
|
||||
expected: map[string]interface{}{"foo": map[string]interface{}{"bar": "bar_1"}},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo_1": "foo_1_1",
|
||||
"foo_2": map[string]interface{}{
|
||||
"foo_2_1": "foo_2_1_1",
|
||||
},
|
||||
"foo_3": map[string]interface{}{
|
||||
"foo_3_1": "foo_3_1_1",
|
||||
"foo_3_2": "foo_3_2_1",
|
||||
"foo_3_3": map[string]interface{}{
|
||||
"foo_3_3_1": "foo_3_3_1_1",
|
||||
"foo_3_3_2": "foo_3_3_2_1",
|
||||
},
|
||||
},
|
||||
"bar": map[string]interface{}{
|
||||
"bar_1": "replacement",
|
||||
},
|
||||
},
|
||||
},
|
||||
path: ".foo.bar.bar_1",
|
||||
value: "bar_1_1",
|
||||
expected: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo_1": "foo_1_1",
|
||||
"foo_2": map[string]interface{}{
|
||||
"foo_2_1": "foo_2_1_1",
|
||||
},
|
||||
"foo_3": map[string]interface{}{
|
||||
"foo_3_1": "foo_3_1_1",
|
||||
"foo_3_2": "foo_3_2_1",
|
||||
"foo_3_3": map[string]interface{}{
|
||||
"foo_3_3_1": "foo_3_3_1_1",
|
||||
"foo_3_3_2": "foo_3_3_2_1",
|
||||
},
|
||||
},
|
||||
"bar": map[string]interface{}{
|
||||
"bar_1": "bar_1_1",
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo_1": "foo_1_1",
|
||||
"foo_2": map[string]interface{}{
|
||||
"foo_2_1": "foo_2_1_1",
|
||||
},
|
||||
"foo_3": map[string]interface{}{
|
||||
"foo_3_1": "foo_3_1_1",
|
||||
"foo_3_2": "foo_3_2_1",
|
||||
"foo_3_3": map[string]interface{}{
|
||||
"foo_3_3_1": "foo_3_3_1_1",
|
||||
"foo_3_3_2": "foo_3_3_2_1",
|
||||
},
|
||||
},
|
||||
"bar": map[string]interface{}{
|
||||
"bar_1": map[string]interface{}{
|
||||
"bar_1_1": map[string]interface{}{
|
||||
"bar_1_1_1": map[string]interface{}{
|
||||
"bar_1_1_1_1": "replacement",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
path: ".foo.bar.bar_1.bar_1_1.bar_1_1_1.bar_1_1_1_1",
|
||||
value: "bar_1_1_1_1_1",
|
||||
expected: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"foo_1": "foo_1_1",
|
||||
"foo_2": map[string]interface{}{
|
||||
"foo_2_1": "foo_2_1_1",
|
||||
},
|
||||
"foo_3": map[string]interface{}{
|
||||
"foo_3_1": "foo_3_1_1",
|
||||
"foo_3_2": "foo_3_2_1",
|
||||
"foo_3_3": map[string]interface{}{
|
||||
"foo_3_3_1": "foo_3_3_1_1",
|
||||
"foo_3_3_2": "foo_3_3_2_1",
|
||||
},
|
||||
},
|
||||
"bar": map[string]interface{}{
|
||||
"bar_1": map[string]interface{}{
|
||||
"bar_1_1": map[string]interface{}{
|
||||
"bar_1_1_1": map[string]interface{}{
|
||||
"bar_1_1_1_1": "bar_1_1_1_1_1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "replacement",
|
||||
},
|
||||
},
|
||||
path: "",
|
||||
value: "bar_1",
|
||||
expected: map[string]interface{}{"foo": map[string]interface{}{"bar": "replacement"}},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
object: &map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "replacement",
|
||||
},
|
||||
},
|
||||
path: "foo.bar",
|
||||
value: "bar_1",
|
||||
expected: map[string]interface{}{
|
||||
"foo": map[string]interface{}{
|
||||
"bar": "replacement",
|
||||
},
|
||||
},
|
||||
expectedErr: errors.New("Invalid path format foo.bar, path should start with a dot"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
err := SetObjectFieldByPath(s.object, s.path, s.value)
|
||||
assert.EqualValues(t, s.expected, *s.object)
|
||||
if s.expectedErr != nil {
|
||||
assert.EqualError(t, err, s.expectedErr.Error())
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue