Internal design of Pronto
Pronto is ruby library which helps you do code review on your changes very conveniently. It works with several linter, including rubocop, reek or brakeman. It’s not only output the issues in our terminal, but also putting comments PR, which reduces a lot of time for us to review styling and convention.
Recently, #hardcore group of Vietnamrb intents to create a similar tool for golang, and the first step for it is study how pronto works internally so that we can make a golang version of it.
Analyse
One of the most common command which we use is:
PRONTO_GITHUB_ACCESS_TOKEN=token pronto run -f github_pr -c origin/master
| | | | | | | |
------------------------------ -------- ---------- --------------
|| || || ||
\/ \/ \/ \/
Access token Main command Formatter Commit
to use in Formatter call Pronto::CLI
We’ll take this command apart and examinate each of them.
-
Firstly, we have
pronto run
, this is the main part of the command, which will callPronto::CLI#run
, this is a class which inherits from Thor. This CLI instance will take the options as arguments, then pass them toPronto.run
. It also checks the current directory, and uses Rugged::Repository to get the current git workdir. Pronto will operate on the current git workdir. -
-f github_pr
indicates a formatter, which helps the CLI generates an instance of a subclass ofPronto::Formatter::Base
, in this case,Pronto::Formatter::GithubPullRequestFormatter
. This instance is responsible for formatting the output of pronto checker, the format can be standard output in terminal, or comments on github commits and pull requests. -
PRONTO_GITHUB_ACCESS_TOKEN=token
set an environment variable which the formatter will use. Each formatter will use different variable. -
-c origin/master
indicates a commit, and the CLI will pass this argument toPronto.run
.
Dive into the Pronto.run
-
Pronto::CLI#run
will passrepo_path
,commit
andformatters
toPronto.run
as arguments. -
repo_path
andcommit
is processed byPronto::Git::Repository
intopatches
, a collection ofPronto::Git::Patch
instances. -
The patch instances are inspected by
Pronto::Runners
, a collection of pronto plugin to run the linter (ex: rubocop, reek or brakeman), then create result which all formatters can read. The result is usually a collection ofPronto::Message
, each of which has the commit sha, file path, line number of the error, severity level of the error and the runner name. -
The
formatters
then read the result and create the expected output.
How Pronto::Runners
work, then?
-
Pronto::Runners.runners
gets all the subclasses ofPronto::Runner::Base
, this behaviour is defined in modulePronto::Plugin
. By default, aPronto::Runners
instance will have all runners to inspect the patches. -
Each runner will run separately through all the valid changes, each time the runner encounters an offence, if that offence is not disabled, runner’ll save it into result.
-
Take
pronto-rubocop
for example, firstly, it usesRubocop::ProcessedSource
to process the whole file and get the offences, then it checks if the offence line number is included in the added lines. After getting all the offences in the added lines, it creates the result with path, line number and severity.
Conclusion
-
The idea of pronto is pretty simple, combines with the elegant implementation, which makes it very convenient for me to study the codebase.
-
From the note above, I’ll need to check the golang ecosystem to see if we can benefit from them like pronto does:
- Linters which has good architecture that separates processing and output formatting, so that we can modify the output easily. I saw that there are golint, govet and metalinter which are very promising.
- Git client similar to rugged so that we can manipulate git repository to get all the patches and changes. Lib2git already published a git client similar to rugged in golang: git2go