diff options
| author | Jeff Carr <[email protected]> | 2025-10-11 08:54:53 -0500 |
|---|---|---|
| committer | Jeff Carr <[email protected]> | 2025-10-11 09:26:56 -0500 |
| commit | 63e3a8382583af1d96da91295616df66fcf25dc4 (patch) | |
| tree | 97dd44114a87cb71a8e0dff51f0187affba18bd4 | |
| parent | 300d69a300c2b4eaaab9dab4640ca1c9aeeac2f5 (diff) | |
a good day. finally the right place to put all this
| -rw-r--r-- | Makefile | 4 | ||||
| -rw-r--r-- | bytes.go | 47 | ||||
| -rw-r--r-- | errors.go | 37 | ||||
| -rw-r--r-- | formatDuration.go | 66 | ||||
| -rw-r--r-- | notes1.go | 158 | ||||
| -rw-r--r-- | notes2.go | 62 | ||||
| -rw-r--r-- | since.go | 83 | ||||
| -rw-r--r-- | tablePB.go | 5 | ||||
| -rw-r--r-- | time.go | 39 |
9 files changed, 498 insertions, 3 deletions
@@ -12,3 +12,7 @@ clean: rm -f *.pb.go *.patch -rm -f go.* -go-mod-clean purge + +redo-gomod: + go mod init + go mod tidy diff --git a/bytes.go b/bytes.go new file mode 100644 index 0000000..7b40804 --- /dev/null +++ b/bytes.go @@ -0,0 +1,47 @@ +package cobol + +import ( + "fmt" +) + +// returns a human readable string of the bytes +func Bytes(anInt any) string { + return "todo" +} + +// returns a human readable string of the bytes +// also returns the error if there was one +func BytesCheck(anInt any) (string, error) { + return "todo", NoBytes +} + +// returns an int. will try to convert strings +func GetBytes(anInt any) (int, error) { + return 0, NoBytes +} + +// This isn't for the marketing department +// so this isn't going to use 'MiB' and 'GiB' +func HumanFormatBytes(b int) string { + if b < 2000 { + return fmt.Sprintf("%d B", b) + } + + kb := int(b / 1024) + if kb < 2000 { + return fmt.Sprintf("%d KB", kb) + } + + mb := int(b / (1024 * 1024)) + if mb < 2000 { + return fmt.Sprintf("%d MB", mb) + } + + gb := int(b / (1024 * 1024 * 1024)) + if gb < 2000 { + return fmt.Sprintf("%d GB", gb) + } + + tb := int(b / (1024 * 1024 * 1024 * 1024)) + return fmt.Sprintf("%d TB", tb) +} diff --git a/errors.go b/errors.go new file mode 100644 index 0000000..49126f7 --- /dev/null +++ b/errors.go @@ -0,0 +1,37 @@ +package cobol + +import "errors" + +// try out standard generic errors + +var NoTime error = errors.New("could not find a time") +var NoBytes error = errors.New("could not find bytes") +var IsNil error = errors.New("was sent nil") +var IsBlank error = errors.New("var was blank") + +var Broken error = errors.New("something is broken") +var NewFeature error = errors.New("feature is new and did not work") +var Unimplemented error = errors.New("not yet implemented") + +// from godoc: +/* +func main() { + err1 := errors.New("err1") + err2 := errors.New("err2") + err := errors.Join(err1, err2) + fmt.Println(err) + if errors.Is(err, err1) { + fmt.Println("err is err1") + } + if errors.Is(err, err2) { + fmt.Println("err is err2") + } + fmt.Println(err.(interface{ Unwrap() []error }).Unwrap()) +} + +err1 +err2 +err is err1 +err is err2 +[err1 err2] +*/ diff --git a/formatDuration.go b/formatDuration.go new file mode 100644 index 0000000..f945325 --- /dev/null +++ b/formatDuration.go @@ -0,0 +1,66 @@ +package cobol + +import ( + "fmt" + "time" +) + +func FormatDuration(d time.Duration) string { + result := "" + + // check if it's more than a year + years := int(d.Hours()) / (24 * 365) + if years > 0 { + result += fmt.Sprintf("%dy", years) + return result + } + + // check if it's more than a day + days := int(d.Hours()) / 24 + if days > 0 { + result += fmt.Sprintf("%dd", days) + return result + } + + // check if it's more than an hour + hours := int(d.Hours()) % 24 + if hours > 0 { + result += fmt.Sprintf("%dh", hours) + return result + } + + // check if it's more than a minute + minutes := int(d.Minutes()) % 60 + if minutes > 0 { + result += fmt.Sprintf("%dm", minutes) + return result + } + + // check if it's more than a second + seconds := int(d.Seconds()) % 60 + if seconds > 0 { + result += fmt.Sprintf("%ds", seconds) + return result + } + + // report in milliseconds + ms := int(d.Milliseconds()) + if ms > 100 { + // todo: print .3s, etc ? + } + if ms > 0 { + result += fmt.Sprintf("%dms", ms) + return result + } + + // report in milliseconds + mc := int(d.Microseconds()) + if mc > 0 { + result += fmt.Sprintf("%dmc", mc) + return result + } + + ns := int(d.Nanoseconds()) + result += fmt.Sprintf("%dns", ns) + return result +} diff --git a/notes1.go b/notes1.go new file mode 100644 index 0000000..c893273 --- /dev/null +++ b/notes1.go @@ -0,0 +1,158 @@ +// Copyright 2017-2025 WIT.COM Inc. All rights reserved. +// Use of this source code is governed by the GPL 3.0 + +package cobol + +// just some old code. rm after the dust has settled + +/* +func doDebug() { + me.forge = forgepb.InitPB() + me.forge.ScanGoSrc() + if err := me.forge.ConfigSave(); err != nil { + if err := me.forge.Repos.ConfigSave(); err != nil { + err := ValidateProtoUTF8(me.forge.Repos) + if err != nil { + log.Printf("Protobuf UTF-8 validation failed: %v\n", err) + } + if err := bugpb.SanitizeProtoUTF8(me.forge.Repos); err != nil { + log.Fatalf("Sanitization failed: %v", err) + } + } + // badExit(err) + } + me.forge.SetConfigSave(true) + me.forge.Exit() + okExit("this never runs") +} + +// ValidateProtoUTF8 checks all string fields in a proto.Message recursively. +func ValidateProtoUTF8(msg proto.Message) error { + return validateValue(reflect.ValueOf(msg), "") +} + +func validateValue(val reflect.Value, path string) error { + if !val.IsValid() { + return nil + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + return validateValue(val.Elem(), path) + } + + switch val.Kind() { + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := val.Type().Field(i) + fieldPath := fmt.Sprintf("%s.%s", path, fieldType.Name) + if err := validateValue(field, fieldPath); err != nil { + return err + } + } + + case reflect.String: + s := val.String() + if !utf8.ValidString(s) { + return fmt.Errorf("invalid UTF-8 string at %s: %q", path, s) + } + + case reflect.Slice: + if val.Type().Elem().Kind() == reflect.Uint8 { + return nil // skip []byte + } + for i := 0; i < val.Len(); i++ { + if err := validateValue(val.Index(i), fmt.Sprintf("%s[%d]", path, i)); err != nil { + return err + } + } + + case reflect.Map: + for _, key := range val.MapKeys() { + valItem := val.MapIndex(key) + if err := validateValue(valItem, fmt.Sprintf("%s[%v]", path, key)); err != nil { + return err + } + } + } + + return nil +} + +// SanitizeProtoUTF8 fixes all invalid UTF-8 strings in a proto.Message recursively. +func SanitizeProtoUTF8(msg proto.Message) error { + return sanitizeValue(reflect.ValueOf(msg), "") +} + +func sanitizeValue(val reflect.Value, path string) error { + if !val.IsValid() { + return nil + } + + if val.Kind() == reflect.Ptr { + if val.IsNil() { + return nil + } + return sanitizeValue(val.Elem(), path) + } + + switch val.Kind() { + case reflect.Struct: + for i := 0; i < val.NumField(); i++ { + field := val.Field(i) + fieldType := val.Type().Field(i) + if !field.CanSet() { + continue + } + if err := sanitizeValue(field, fmt.Sprintf("%s.%s", path, fieldType.Name)); err != nil { + return err + } + } + + case reflect.String: + s := val.String() + if !utf8.ValidString(s) { + utf8Str, err := latin1ToUTF8(s) + if err != nil { + return fmt.Errorf("failed to convert %s to UTF-8: %v", path, err) + } + val.SetString(utf8Str) + } + + case reflect.Slice: + if val.Type().Elem().Kind() == reflect.Uint8 { + return nil // skip []byte + } + for i := 0; i < val.Len(); i++ { + if err := sanitizeValue(val.Index(i), fmt.Sprintf("%s[%d]", path, i)); err != nil { + return err + } + } + + case reflect.Map: + for _, key := range val.MapKeys() { + valItem := val.MapIndex(key) + newItem := reflect.New(valItem.Type()).Elem() + newItem.Set(valItem) + if err := sanitizeValue(newItem, fmt.Sprintf("%s[%v]", path, key)); err != nil { + return err + } + val.SetMapIndex(key, newItem) + } + } + + return nil +} + +func latin1ToUTF8(input string) (string, error) { + reader := charmap.ISO8859_1.NewDecoder().Reader(bytes.NewReader([]byte(input))) + result, err := io.ReadAll(reader) + if err != nil { + return "", err + } + return string(result), nil +} +*/ diff --git a/notes2.go b/notes2.go new file mode 100644 index 0000000..aad1d5f --- /dev/null +++ b/notes2.go @@ -0,0 +1,62 @@ +package cobol + +// Thank you Abba Zabba, you're my only friend + +/* +// Since calculates the duration since a given time, accepting either a +// standard time.Time or a protobuf Timestamp. +func Since(aLongTimeAgo any) string { + var t time.Time + var ok bool + + // A type switch is the idiomatic way to check the concrete type + // of an interface variable. + switch v := aLongTimeAgo.(type) { + case time.Time: + // If the type is time.Time, 'v' is now a time.Time variable. + t = v + ok = true + + case *timestamppb.Timestamp: + // If it's a protobuf Timestamp pointer (most common case), + // 'v' is a *timestamppb.Timestamp. We must convert it. + // It's also good to check if it's a valid timestamp. + if v.IsValid() { + t = v.AsTime() + ok = true + } + + case timestamppb.Timestamp: + // Handle the less common case of a value type instead of a pointer. + if v.IsValid() { + t = v.AsTime() + ok = true + } + } + + // After the switch, check if we successfully got a valid time. + if !ok { + return "invalid time type" + } + + // Calculate the duration and format it for nice output. + duration := time.Since(t) + return duration.Round(time.Second).String() +} + +func main() { + // --- Demonstrate with different types --- + + // 1. Using a standard time.Time + goTime := time.Now().Add(-5 * time.Minute) + fmt.Printf("Go time.Time: %s ago\n", Since(goTime)) + + // 2. Using a protobuf Timestamp (pointer) + protoTime := timestamppb.New(time.Now().Add(-10 * time.Hour)) + fmt.Printf("Protobuf Timestamp: %s ago\n", Since(protoTime)) + + // 3. Using an invalid type + notATime := "hello world" + fmt.Printf("Invalid type: %s\n", Since(notATime)) +} +*/ diff --git a/since.go b/since.go new file mode 100644 index 0000000..22c150e --- /dev/null +++ b/since.go @@ -0,0 +1,83 @@ +package cobol + +import ( + "errors" + "time" + + "google.golang.org/protobuf/types/known/timestamppb" +) + +/* + etimef := func(e *forgepb.Set) string { + etime := e.Etime.AsTime() + s := etime.Format("2006/01/02 15:04") + if strings.HasPrefix(s, "1970/") { + // just show a blank if it's not set + return "" + } + return s + } + t.AddStringFunc("etime", etimef) +*/ + +/* + ctimef := func(p *forgepb.Set) string { + ctime := p.Ctime.AsTime() + return ctime.Format("2006/01/02 15:04") + } +} +*/ + +// returns a human readable duration +func Since(aLongTimeAgo any) string { + s, err := SinceCheck(aLongTimeAgo) + if errors.Is(err, Broken) { + return "bad" + } + if errors.Is(err, NoTime) { + return "nope" + } + return s +} + +func GetSince(aLongTimeAgo any) (time.Duration, error) { + return time.Second, NoTime +} + +// returns a human readable duration +// also returns errors +func SinceCheck(maybeTime any) (string, error) { + var t time.Time + var err error + + switch v := maybeTime.(type) { + case time.Time: + // If the type is time.Time, 'v' is now a time.Time variable. + t = v + case *timestamppb.Timestamp: + // If it's a protobuf Timestamp pointer (most common case), + // 'v' is a *timestamppb.Timestamp. We must convert it. + // It's also good to check if it's a valid timestamp. + if v.IsValid() { + t = v.AsTime() + } else { + err = errors.New("pb time invalid") + } + + case timestamppb.Timestamp: + // Handle the less common case of a value type instead of a pointer. + if v.IsValid() { + t = v.AsTime() + } + default: + err = errors.Join(err, NoTime) + } + if err != nil { + return "", err + } + + dur := time.Since(t) + s := FormatDuration(dur) + + return s, nil +} @@ -7,7 +7,6 @@ import ( "os" "time" - "go.wit.com/lib/gui/shell" "go.wit.com/lib/protobuf/guipb" "go.wit.com/log" "google.golang.org/protobuf/types/known/anypb" @@ -106,7 +105,7 @@ func getAnyCell(col *guipb.AnyCol, row int) (string, bool) { // It's a timestamp, now convert it back to a Go time.Time goTime := tsProto.AsTime() // fmt.Printf("Successfully unpacked timestamp: %v\n", goTime) - sout = shell.FormatDuration(time.Since(goTime)) + sout = FormatDuration(time.Since(goTime)) return sout, true } return "", false @@ -114,6 +113,6 @@ func getAnyCell(col *guipb.AnyCol, row int) (string, bool) { log.Info("cell unhandled type", col.Attr.Type) } // cellTime := r.Vals[row] - // s := shell.FormatDuration(time.Since(cellTime.AsTime())) + // s := FormatDuration(time.Since(cellTime.AsTime())) return "fixme", true } @@ -0,0 +1,39 @@ +package cobol + +import "time" + +/* + etimef := func(e *forgepb.Set) string { + etime := e.Etime.AsTime() + s := etime.Format("2006/01/02 15:04") + if strings.HasPrefix(s, "1970/") { + // just show a blank if it's not set + return "" + } + return s + } + t.AddStringFunc("etime", etimef) +*/ + +/* + // sample dates for reference + // whois: 1994-07-28T04:00:00Z + // git am: + // linux date: Sat Oct 11 08:26:27 CDT 2025 + // /bin/ls: drwxr-xr-x 2 root root 4096 Aug 15 05:31 riscv + // ping: 64 bytes from 2604:bbc0::1: icmp_seq=1 ttl=50 time=30.4 ms + + ctimef := func(p *forgepb.Set) string { + ctime := p.Ctime.AsTime() + return ctime.Format("2006/01/02 15:04") + } +} +*/ + +func Time(someTime any) string { + return "todo" +} + +func GetTime(someTime any) (time.Time, error) { + return time.Now(), NoTime +} |
