versions
was officially announced to the world last month, and since then I’ve been working on adding features I think will make this tool more useful.
Today I’m introducing a new minor version: v0.1.0
, but before that…
What did change?
Architecturally versions
is pretty straightforward, it consists in the following:
- Receive an input of
go.mod
files, which represent “projects” or “repositories”, - Read and parse those
go.mod
files with the goal of generating a matrix of packages, - Categorize the packages found in each
go.mod
file, - Determine a way to indicate which packages have the same and/or different versions,
- Print out the results.
v0.0.1
was built with those steps in mind. The initial goal was to quickly implement a proof of concept, an actionable prototype that worked with one objective: to render flavored markdown. This objective was accomplished.
However, this code was not in a shape to be easily extented, sure it did the job, but it required more thought if the plan was to move forward and continue with the development. With that in mind my next goals before making a new release were two:
- Clean up and refactor the code to make it easier to read and extend, in order to support rendering multiple outputs, flavored markdown being the first one.
- Add a new feature that could be used by any rendering output, this is to make sure the new refactored code was developer friendly.
Cleaning up and refactoring
It was clear that separating the actual parsing from the rendering (and its concrete rules) was needed. The first version assumed flavored markdown was the only rendering output supported, it included concrete rendering rules applicable to this type and it was intertwined with other non-rendering logic. This change was required for allowing adding more ouputs in the near future, like JSON.
In v0.0.1
really everything was implemented in two functions:
versions.NewGoMods
for doing something with the parsedgo.mod
values, andversions.PrintMarkdown
for printing out the actual markdown code.
So, how we go from that really concrete implementation to something more abstract?
The way I thought about it in v0.1.0
was to define the final results in a new Versions
type:
// Versions contains the parsed go.mod files.
Versions struct {
Modules map[ModuleName]Module
GoVersions GoVersions
Packages Packages
}
The fields in this type contain the parsed values organized in threee different ways:
- Specifically what each
go.mod
file contains,Modules
field, this includes theModule
s:- Names and Go Versions in
ModuleGoVersion
s, and its - Dependency Requirements in the form of a map of
Package
s.
- Names and Go Versions in
- All packages being used,
Packages
field, organized internally byModuleName
, - All Go Versions used,
GoVersions
field.
Having those three fields available are the basic layer required for allowing different renderers to generate what is needed. markdown
is the concrete example so far, this package only renders the results, it still supports the options that were available before but now they don’t affect the parsing process.
Adding a new feature
After cleaning up the code and refactoring it, the second goal was to add a new feature. I thought that adding LICENSE
support would be a good excercise, because:
- It touches a basic type:
Package
, because licenses are per package and could change depending on the concrete version used, - It adds (hypothetically) new dependencies, and
- It could affect how rendering outputs are implemented.
Therefore adding a feature like this will allow me, the user of the versions
package, to determine how good or bad the user experience was (from the development perspective that is), to corroborate whether the final refactored code is better or not.
In the end this was an interesting excercise that proved that the code was easier to work with and also led to another nice change, the introduction of olekukonko/tablewriter
for rendering markdown code in a more user-readable way.
Conclusion
This time I wanted to diverge a tiny bit from the usual announcement-like post and instead walk you through my thought process and how the code evolved. For sure versions
is still in its infancy but I think it makes sense to describe the behind-the-scenes from time to time.
To install the current version you can run:
GO111MODULE=on go get github.com/MarioCarrion/versions/cmd/versions@v0.1.0
Using it is the same as before:
versions <full path to 1 go.mod> <full path to 2 go.mod> <full path to N go.mod>
Have fun!