vij update

Vij is a bash function that makes editing specific files easier to find in a deeply nested directory structure. At the time I was working in Java which is notorious for having a deep directory structure. In 2008, I updated vij to be more powerful by offering a list of files matching the name, giving you the option to make a choice.

Our current project is in Golang and I’ve found a deficiency in vij. We have a vendor directory which contains our dependencies and sometimes vij will find files in the vendor directory which I don’t want to edit. It would be nice to exclude a directory from the find command used by vij.

I always want to ignore the vendor directory. But I also want to allow arbitrary names to also be filtered out. The existing vij implementation results in the following crazy output.

$ vij util.go

Multiple matches found...
1: ./pkg/broker/util.go
2: ./pkg/fusortest/util.go
3: ./vendor/k8s.io/kubernetes/vendor/golang.org/x/crypto/ssh/terminal/util.go
4: ./vendor/k8s.io/kubernetes/vendor/github.com/vmware/photon-controller-go-sdk/photon/util.go
5: ./vendor/k8s.io/kubernetes/vendor/github.com/spf13/viper/util.go
6: ./vendor/k8s.io/kubernetes/vendor/github.com/spf13/cobra/doc/util.go
...
16: ./vendor/k8s.io/kubernetes/vendor/github.com/kr/pty/util.go
...
108: ./vendor/github.com/coreos/etcd/client/util.go
q: Quit

? 

I just want to see the util.go files in my code tree and ignore all of the ones in vendor. I changed the dafiles variable to have two more options. One is always ignore vendor from the normal use case, but also allow the user to pass in a string to filter on.

    if [ "$3" == "" ]; then
        dafiles=$(find . -type f -name "$2" -not -path "*/vendor/*")
    else 
        dafiles=$(find . -type f -name "$2" | grep -v $3)
    fi

Now when I run vij util.go, vij ignores the vendor directory.

$ vij util.go

Multiple matches found...
1: ./pkg/broker/util.go
2: ./pkg/fusortest/util.go
q: Quit

? 

Let’s say I want to find all files named User*.java but not the ones in the test directory. You can simply supply “test” as the second argument to vij.

BEFORE

$ vij User*.java

Multiple matches found...
1: ./src/main/java/org/candlepin/auth/permissions/UserUserPermission.java
2: ./src/main/java/org/candlepin/auth/permissions/UsernameConsumersPermission.java
3: ./src/main/java/org/candlepin/auth/UserAuth.java
4: ./src/main/java/org/candlepin/auth/UserPrincipal.java
5: ./src/main/java/org/candlepin/model/User.java
6: ./src/main/java/org/candlepin/model/UserCurator.java
7: ./src/main/java/org/candlepin/resource/UserResource.java
8: ./src/main/java/org/candlepin/service/UserServiceAdapter.java
9: ./src/test/java/org/candlepin/auth/UserPrincipalTest.java
10: ./src/test/java/org/candlepin/auth/permissions/UsernameConsumersPermissionTest.java
11: ./src/test/java/org/candlepin/model/UserTest.java
12: ./src/test/java/org/candlepin/resource/UserResourceTest.java
q: Quit

? 

AFTER

$ vij User*.java test

Multiple matches found...
1: ./src/main/java/org/candlepin/auth/permissions/UserUserPermission.java
2: ./src/main/java/org/candlepin/auth/permissions/UsernameConsumersPermission.java
3: ./src/main/java/org/candlepin/auth/UserAuth.java
4: ./src/main/java/org/candlepin/auth/UserPrincipal.java
5: ./src/main/java/org/candlepin/model/User.java
6: ./src/main/java/org/candlepin/model/UserCurator.java
7: ./src/main/java/org/candlepin/resource/UserResource.java
8: ./src/main/java/org/candlepin/service/UserServiceAdapter.java
q: Quit

? 

Here’s the full implementation of the updated vij:

# reused by other commands that require a filename
__dafiles ()
{
    cmd=$1
    if [ "$3" == "" ]; then
        dafiles=$(find . -type f -name "$2")
        #dafiles=$(find . -type f -name "$2" -not -path "*/vendor/*")
    else
        dafiles=$(find . -type f -name "$2" | grep -v $3)
    fi
    matches=$(echo $dafiles | gawk '{print NF}')

    case "$matches" in
        0)
           echo "No matches found"
           show=""
           ;;
        1)
           show=$dafiles
           ;;
        *)
           echo
           echo "Multiple matches found..."
           i=1
           for option in $dafiles
           do
              echo "$i: $option"
              i=`expr $i + 1`
           done
           echo "q: Quit"
           echo 
           read -p "? " ans
           if [ "q" == "$ans" ]; then
              show=""
           else
              show=$(echo $dafiles | gawk '{print $'$ans'}')
           fi
           ;;
    esac

    if [ "" != "$show" ]; then
       $cmd $show
    fi
}

vij ()
{
    __dafiles "vim" $1 $2
}

More Go links

Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files.

https://github.com/spf13/cobra

Viper is a complete configuration solution for go applications including 12 factor apps. It is designed to work within an application, and can handle all types of configuration needs and formats.

https://github.com/spf13/viper

pflag is a drop-in replacement for Go’s flag package, implementing POSIX/GNU-style –flags.

https://github.com/spf13/pflag

7 common mistakes in Go and when to avoid them
https://www.youtube.com/watch?v=29LLRKIL_TI

Go! Speed Racer Go!

I finally reached a point where I could start running the go version of sm-photo-tool. I finished the option validation for the list command. While I was testing it I noticed how much faster the Go version felt. Here are the python vs Go versions of the commands.

The python version took 80 milliseconds to run the validation of parameters and print out the error message. This is expected, you have to start the python VM (and if not precompiled, the code would need to be compiled). In this test, the code was precompiled.

$ time sm-photo-tool list invalid blah
ERROR: valid options are ['album', 'galleries']

real	0m0.080s
user	0m0.059s
sys	0m0.021s

Ok so how fast is the Go version? My guess was half the time, 40ms. I was way off. Try 6ms. SIX! That’s amazing to do the exact same amount of work.

$ time ./sm-photo-tool list invalid blah
ERROR: valid options are [album galleries]

real	0m0.006s
user	0m0.001s
sys	0m0.005s

Private Golang, reporting for duty

Many modern programming languages have a mechanism for controlling the visibility of members and methods. Most of the languages use keywords like private and public.

Java and C++ use public, private, and protected. Ruby also uses keywords: private, and protected.

Python on the other hand uses convention to control visibility, prefixing your methods with _ or __. Even then it’s more of a suggestion in Python as you can call it anyway. There is nothing to prevent it from actually being called from other code. It’s an understanding among Python developers that if there is an underscore you shouldn’t call it.

Go also has no keyword to control visibility. What no keyword? There has to be something? Maybe it uses _ like python? Nope that won’t do anything. Then what would Go use to control visibility? Go uses a very simple syntax, capitalization.

CAPITALIZATION? What? Yes, that’s correct. A simple capital letter makes the member or method public. Lowercase member, methods are private.

// GetName is a public method of User struct
func (u *User) GetName() string {
  return u.name
}

// nickName is a private method of User struct
func (u *User) nickName() {
  // do something
}

The same logic applies to members. Given the following example struct, Foo is public and bar is private:

type Example struct {
  Foo string
  bar string
}

So if you want to make your Go methods or members private, just start them with a lowercase letter. Want others to use it, capitalize it.

Constructors? Not in Go

If you’ve used any object-oriented language in the past decade, C++, Java, Python, Ruby, C#, you are used to defining a class and each having its own constructor. Here’s a sample in Java and Python, respectively.

public class User {
    private String username;

    public User(String name) {
        username = name
    }
    // ...
}
class User(object):
    def __init__(self, name):
        self.username = name

Previously, I wrote about Go having no class. Go uses structs. I covered how to write a struct and add methods to it. But now how do you create one? Let’s continue with the user example from above. In Go, we’d create a user struct:

type User struct {
    username string
}

Ok that was easy. But what about the constructor? Go doesn’t use constructors as you are accustomed. Go will basically initialize everything to their ZERO value, i.e. ints are 0, strings are empty. structs will have their members set to their respective zero values. Let’s look at the User example above.

u := User{}

This creates the User u instance with the username set to the zero value for strings, “”.

Sometimes the zero value isn’t enough. Sometimes you want to pass in values you know you need for that particular instance. That’s pretty simple too, again let’s look at the User struct:

u : User{"joeuser"}

This will set the username to joeuser. What if the struct had more attributes? You can either specify all the values in order or by name and value. Let’s add the age attribute to the User struct.

type User struct {
    username string
    age int
}

Initialize both by passing all the values:

u := User{"joeuser", 30}

Alternatively, we can initialize the username and leave the age to be it’s default zero value.

u := User{username: "joeuser"}

But what about something more complex than a two field User object? Since Go doesn’t have constructors, you have to create what I like call a “pseudo constructor”, basically a method that returns the initialized struct.

func NewUser(name string, age int) *User {
    // do whatever work you need to do
    // calculate fields, set defaults, etc.
    if age < 18 {
        fmt.Println("user is a minor")
    }
    return &User{name, age}
}

The name can be anything, I like to use NewStructName as a convention to make it easier to see what they are for.

While Go doesn’t have constructors, there are still three ways you can “construct” objects:

  • using the zero values
  • using the {} to pass in the values to the struct
  • creating a construction function to build the struct

Now “Go” and construct some objects.

Go has no class

Coming from Java, Python, and Ruby, I’m used to working with objects and methods. Go doesn’t have classes but it does have structs that you can add methods to it.

In Java, you would typically do the following:

public class User {
  private String name;

  public User(String name) {
    this.name = name;
  }

  // method with return value
  public String getName() {
    return this.name;
  }

  // method with argument
  public void setName(String name) {
    this.name = name;
  }

  // method with argument and return value
  public int argWithReturn(String anarg) {
    return anarg.length();
  }
}

In Go you would use a struct.

type User struct {
    name string
}

The weirdest part is that you don’t define the methods in the struct block. How do you add methods to the User struct? Simply create a function and add (u *User) to it.

func (u *User) GetName() string {
    return u.name
}

func (u *User) SetName(nm string) {
    u.name = nm
}

func (u *User) ArgWithReturn(anarg string) int {
    return len(anarg)
}

Whoa, that’s strange. Why does GetName return a *User type? It doesn’t, the return type is defined at the end of the definition. So GetName actually returns a string. The (u *User) is how you tell Go these methods are attached to the User struct.

I think I’ll look either into packages or argument parsing.

Adventures with the Go programming language

After 10+ years of working with Java, I spent the last year working with Ruby & Rails. Let’s just say I’m not a fan. I wanted to learn something new and I’ve heard a lot of good things about Go. Quite a few projects use Go: Docker, Hugo, Kubernetes, Prometheus, and many more.

Three weeks ago, I picked up a copy of “The Go Programming Language” by Donovan & Kernighan. Yes, the same Kernighan that co-wrote the famous, “The C Programming Language”.

In order to learn it, I needed a real life scenario, I needed a project. Why not port, sm_photo_tool, my Smugmug command line tool from python to Go lang? That will give me experience with how Go handles parsing arguments, classes, REST APIs, concurrent uploads, and a bunch more things.  It’s the perfect project to learn Go.

In the next post I’ll talk about structs in Go.