summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Carr <[email protected]>2025-10-11 08:54:53 -0500
committerJeff Carr <[email protected]>2025-10-11 09:26:56 -0500
commit63e3a8382583af1d96da91295616df66fcf25dc4 (patch)
tree97dd44114a87cb71a8e0dff51f0187affba18bd4
parent300d69a300c2b4eaaab9dab4640ca1c9aeeac2f5 (diff)
a good day. finally the right place to put all this
-rw-r--r--Makefile4
-rw-r--r--bytes.go47
-rw-r--r--errors.go37
-rw-r--r--formatDuration.go66
-rw-r--r--notes1.go158
-rw-r--r--notes2.go62
-rw-r--r--since.go83
-rw-r--r--tablePB.go5
-rw-r--r--time.go39
9 files changed, 498 insertions, 3 deletions
diff --git a/Makefile b/Makefile
index 72f0e65..a9beaf7 100644
--- a/Makefile
+++ b/Makefile
@@ -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
+}
diff --git a/tablePB.go b/tablePB.go
index 8bd5cc2..99102c1 100644
--- a/tablePB.go
+++ b/tablePB.go
@@ -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
}
diff --git a/time.go b/time.go
new file mode 100644
index 0000000..68ce878
--- /dev/null
+++ b/time.go
@@ -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
+}