Is it possible to augment fmt.print with default indentation level?

1.1k views Asked by At

I am messing around creating modules and interlinking them.

When testing out behaviours I am calling packages that call other packages.

Is there an easy way to modify behaviour of fmt package with indentation level of future calls. So that parent can sets indentation level +1 before calling child package.

This way when each function prints out output I can easily see cascading dependencies in the stdout:

inside main
calling package X
____entering package X
____calling package Y
________package Y hello world
____leaving package X
back in main.
exiting 
2

There are 2 answers

0
icza On BEST ANSWER

The fmt package does not support this out of the box.

However, you can access the call depth with the help of runtime.Callers(), which means you don't even have to maintain the indentation "level" manually.

"Proof of concept"

Accessing the call depth is like this (which returns 0 for main(), 1 for a function called from main() etc.):

func callDepth() int {
    pc := make([]uintptr, 100)
    return runtime.Callers(6, pc)
}

And using it, an auto-indenter printing function:

var tabs = strings.Repeat("\t", 100)

func Println(args ...interface{}) {
    fmt.Print(tabs[:callDepth()])
    fmt.Println(args...)

}

Let's see it in action:

func main() {
    Println("In main()")
    f1()
    Println("In main() again")
}

func f1() {
    Println("In f1()")
    f2()
    Println("In f1() again")
}

func f2() {
    Println("In f2()")
}

Which outputs (try it on the Go Playground):

In main()
    In f1()
        In f2()
    In f1() again
In main() again

Notes

I called the above solution "proof of concept" because it's not a solution handling all cases. You have to decide how you want to handle when new goroutines are launched. When new goroutines are launched, they are not called from main(), so the skip frames passed to runtime.Callers() should be 1-less (runtime.Callers(5, pc) instead of runtime.Callers(6, pc)). For how to detect this, see Check if function is being called as goroutine or not.

0
Vorsprung On

Write a little wrapper around the fmt functions you want to behave like this so they take an extra parameter of indent level

package main

import "fmt"

// Println  replacement
func Println(level int, args ...interface{}) {
    for i := level; i > 0; i-- {
        fmt.Print("__")
    }
    fmt.Println(args...)
}

func main() {
    Println(0, "here")
    Println(1, "there")
    Println(2, "everywhere")
    Println(2, "Yes, everywhere")
}