Integrate vale into an open source project
This week my friend Bekah introduced me to vale.sh, an open-source spelling and grammer checker written in golang and configurable to your project’s needs.
Think grammarly, but as a CLI tool.
You can even add it to your CI/CD build.
Installing vale
The vale.sh site includes a friendly Get Started
button which takes you to some Installation docs.
On my M1 Mac this was as simple as using brew:
brew install vale
This installed the vale
CLI and made it avaialble in my PATH, so I could open a terminal and use it right away:
❯ which vale
/opt/homebrew/bin/vale
❯ vale
vale - A command-line linter for prose.
Usage: vale [options] [input...]
vale myfile.md myfile1.md mydir1
vale --output=JSON [input...]
Vale is a syntax-aware linter for prose built with speed and extensibility in
mind. It supports Markdown, AsciiDoc, reStructuredText, HTML, and more.
To get started, you'll need a configuration file (.vale.ini):
Example:
StylesPath = a/path/to/your/styles
MinAlertLevel = suggestion
[*]
BasedOnStyles = Vale
See https://vale.sh for more setup information.
(Or use vale --help for a listing of all CLI options.)
Configuring vale
Configuring vale
took me some time as I learned a few things about the tool:
- it requires a configuration file to work (default file is
.vale.ini
in whatever directory you are currently in) and this can be set per-project and checked in under source control - it requires a set of yaml styles to be available on your local machine
- it includes a
sync
subcommand to download styles based on your configuration file
Here is what I recommend:
-
Visit this vale Config Generator and experiment with the dropdown boxes to generate a
.vale.ini
file.Here is the one I selected for my project:
StylesPath = .styles MinAlertLevel = suggestion Vocab = Base Packages = Google, write-good [*] BasedOnStyles = Vale, Google, write-good
Note that I changed the default
StylesPath
setting fromstyles
to.styles
. This means thatvale
will look for styles yaml files inside the.styles
folder of my project.Prefixing a file or folder name with a
.
means that the file or folder will be hidden from a normal directory listing, similar to the.git
folder.I made this choice because I don’t want to distract first time contributors with a
styles
folder that is part of my project’s standards but not part of my project’s content. -
Save this file as
.vale.ini
in the root of your project folder.❯ cd my-project ❯ ls .vale.ini .vale.ini ❯ cat .vale.ini StylesPath = .styles MinAlertLevel = suggestion Vocab = Base Packages = Google, write-good [*] BasedOnStyles = Vale, Google, write-good ❯
-
Open a terminal, change directory into the root of your project folder, and run this
vale sync
Here’s what it looked like for me with the
.vale.ini
file above:❯ cd my-project ❯ vale sync SUCCESS Downloaded package 'Google' SUCCESS Downloaded package 'write-good' Downloading packages [2/2] ❯
This uses the settings in your project’s .vale.ini
to download styles yaml files and put them in your local StylesPath
.
With the .vale.ini
file in my project folder that looks like this:
❯ cd my-project
❯ ls .
.styles .vale.ini
❯ tree .styles
.styles
├── Google
│ ├── AMPM.yml
│ ├── Acronyms.yml
│ ├── Colons.yml
│ ├── Contractions.yml
│ ├── DateFormat.yml
│ ├── Ellipses.yml
│ ├── EmDash.yml
│ ├── EnDash.yml
│ ├── Exclamation.yml
│ ├── FirstPerson.yml
│ ├── Gender.yml
│ ├── GenderBias.yml
│ ├── HeadingPunctuation.yml
│ ├── Headings.yml
│ ├── Latin.yml
│ ├── LyHyphens.yml
│ ├── OptionalPlurals.yml
│ ├── Ordinal.yml
│ ├── OxfordComma.yml
│ ├── Parens.yml
│ ├── Passive.yml
│ ├── Periods.yml
│ ├── Quotes.yml
│ ├── Ranges.yml
│ ├── Semicolons.yml
│ ├── Slang.yml
│ ├── Spacing.yml
│ ├── Spelling.yml
│ ├── Units.yml
│ ├── We.yml
│ ├── Will.yml
│ ├── WordList.yml
│ ├── meta.json
│ └── vocab.txt
├── Vocab
│ └── Base
│ ├── accept.txt
│ └── reject.txt
└── write-good
├── Cliches.yml
├── E-Prime.yml
├── Illusions.yml
├── Passive.yml
├── README.md
├── So.yml
├── ThereIs.yml
├── TooWordy.yml
├── Weasel.yml
└── meta.json
4 directories, 46 files
Note: The tree
command is a unix command to print out a folder structure as a tree shape. On MacOS the command is available via brew install tree
I checked in both .vale.ini
and the entire .styles
folder into source control so that anyone who clones or forks this project is using the same style files; there are lots of style files but yaml is pretty compact when stored in .git
Using vale
Now that you have the prerequisites installed:
vale
CLI- a valid
.vale.ini
file - style yaml files downloaded to the
StylesPath
configured in your.vale.ini
file
you are ready to use vale
to validate (valedate?) your content.
So far I have only used vale
on Markdown files.
Running vale
on my preject’s README originally looked like this:
❯ vale README.md
README.md
5:13 suggestion Spell out 'GPL', if it's Google.Acronyms
unfamiliar to the audience.
13:20 warning 'go-git-mob' should use Google.Headings
sentence-style capitalization.
16:5 error Did you really mean 'golang'? Vale.Spelling
16:24 error Did you really mean 'nodejs'? Vale.Spelling
33:46 warning 'Table of Contents' should use Google.Headings
sentence-style capitalization.
50:4 warning 'About The Project' should use Google.Headings
sentence-style capitalization.
52:14 suggestion Try to avoid using 'is'. write-good.E-Prime
52:54 suggestion Feel free to use 'it's' Google.Contractions
instead of 'It is'.
52:54 warning 'It is' is too wordy. write-good.TooWordy
52:62 error Did you really mean 'golang'? Vale.Spelling
52:81 error Did you really mean 'nodejs'? Vale.Spelling
52:121 error Did you really mean Vale.Spelling
'complimenents'?
53:52 suggestion Try to avoid using 'be'. write-good.E-Prime
53:52 warning 'be detected' may be passive write-good.Passive
voice. Use active voice if you
can.
53:52 suggestion In general, use active voice Google.Passive
instead of passive voice ('be
detected').
53:186 error Did you really mean 'sesson'? Vale.Spelling
54:53 warning Avoid using 'will'. Google.Will
54:58 suggestion In general, use active voice Google.Passive
instead of passive voice ('be
squashed').
54:58 warning 'be squashed' may be passive write-good.Passive
voice. Use active voice if you
can.
54:58 suggestion Try to avoid using 'be'. write-good.E-Prime
60:8 warning Try to avoid using Google.We
first-person plural like 'we'.
60:26 warning Try to avoid using Google.We
first-person plural like 'we'.
60:135 suggestion Try to avoid using 'was'. write-good.E-Prime
60:277 warning 'only' is a weasel word! write-good.Weasel
60:285 suggestion In general, use active voice Google.Passive
instead of passive voice ('be
left').
60:285 suggestion Try to avoid using 'be'. write-good.E-Prime
60:285 warning 'be left' may be passive write-good.Passive
voice. Use active voice if you
can.
60:320 warning 'In addition' is too wordy. write-good.TooWordy
60:375 suggestion In general, use active voice Google.Passive
instead of passive voice ('was
introduced').
60:375 suggestion Try to avoid using 'was'. write-good.E-Prime
60:375 warning 'was introduced' may be write-good.Passive
passive voice. Use active
voice if you can.
60:467 suggestion Try to avoid using 'be'. write-good.E-Prime
62:14 suggestion In general, use active voice Google.Passive
instead of passive voice ('was
created').
62:14 warning 'was created' may be passive write-good.Passive
voice. Use active voice if you
can.
62:14 suggestion Try to avoid using 'was'. write-good.E-Prime
62:105 suggestion In general, use active voice Google.Passive
instead of passive voice ('be
installed').
62:105 warning 'be installed' may be passive write-good.Passive
voice. Use active voice if you
can.
62:105 suggestion Try to avoid using 'be'. write-good.E-Prime
62:167 error Did you really mean 'nodejs'? Vale.Spelling
62:205 error Did you really mean 'repo'? Vale.Spelling
64:5 warning 'Built With' should use Google.Headings
sentence-style capitalization.
70:4 warning 'Getting Started' should use Google.Headings
sentence-style capitalization.
76:8 suggestion Use parentheses judiciously. Google.Parens
76:9 warning Try to avoid using Google.We
first-person plural like 'we'.
76:9 suggestion Feel free to use 'we're' Google.Contractions
instead of 'we are'.
76:12 suggestion Try to avoid using 'are'. write-good.E-Prime
83:5 warning 'Utility Commands' should use Google.Headings
sentence-style capitalization.
85:38 warning 'a number of' is too wordy. write-good.TooWordy
107:105 suggestion Use parentheses judiciously. Google.Parens
128:12 error Did you really mean 'Kotze'? Vale.Spelling
128:27 error Did you really mean 'Ideler'? Vale.Spelling
128:149 error Did you really mean 'nodejs'? Vale.Spelling
✖ 11 errors, 20 warnings and 21 suggestions in 1 file.
❯
Wow that was a lot of feedback. In the end, however, it did not take me very long to work through each piece of feedback and resolve them.
Accepting proper nouns and domain specific language
Some of the errors were spelling errors with unrecognized proper nouns or technology keywords.
I learned that you can add such proper nouns and known words to your local styles files by adding them to a $StylesPath/Vocab/Base/accept.txt
file.
Here is what mine looks like for the go-git-mob
project:
Github
Make
make
Makefile
rbenv
nodenv
asdf
aruba
Golang
golang
nodejs
go-git-mob
git-mob
GPL
url
Kotze
Idele
These words are no longer flagged as Vale.Spelling
errors.
Disabling linting rules with markdwn comments
Another neat trick is that vale
supports disabling and enabling all validation or just specific rules using special comments in the same way that other linters (e.g. eslint
, rubocop
, etc) do.
Disable one rule
For example:
<!-- vale Google.Headings = NO -->
<h1 align="center">go-git-mob</h1>
<!-- vale Google.Headings = YES -->
disables the Google.Headings
validation rule for that one top-level heading, which I do because I know that go-git-mob
is the name of the project.
Disable all validation
Occasionally you may want to disable all validation, such as when including a blockquote.
You don’t want style/linting rules for your project to cause you to misquote someone else.
In this case you can temporarily disable all validation by surrounding your blockquote like this:
<!-- vale off -->
<blockquote>...</blockquote>
<!-- vale on -->
Set up guard for rapid feedback
I like rapid feedback, so I set up the guard
command-line tool to re-run validation every time my README.md
or CONTRIBUTING.md
files chagne.
I added these two gems to my project’s Gemfile
:
group :development do
gem 'guard'
gem 'guard-process'
end
And these lines to a ./Guardfile
:
guard 'process', :name => 'Valedation', :command => 'vale README.md CONTRIBUTNG.md' do
watch('README.md')
watch('CONTRIBUTING.md')
end
Now I can cd
into my project folder and run guard
to enable this file watching behaviour:
❯ bundle exec guard
14:02:48 - INFO - Starting process Valedation
14:02:48 - INFO - Started process Valedation
14:02:48 - INFO - Guard is now watching at '/Users/dalpert/projects/go-git-mob'
[1] guard(main)> ✔ 0 errors, 0 warnings and 0 suggestions in 2 files.
And any time the guard
tool detects a change to one of those markdown files it will automatically re-run validation:
14:04:38 - INFO - Stopping process Valedation
14:04:38 - INFO - Stopped process Valedation
14:04:38 - INFO - Starting process Valedation
14:04:38 - INFO - Started process Valedation
[1] guard(main)> ✔ 0 errors, 0 warnings and 0 suggestions in 2 files.
This was helpful when resolving the initial linting feedback but also when editing, to have updated linting output every time you save a file.
When resolving the first round of errors I ran guard
in a terminal window inside VSCode and worked on the last errors/warnings first. As I fixed them and saved the file, the guard
script would trigger and update the validation leaving new errors/warnings at the bottom of the list. This let me keep my terminal window small and the editing window large and usually focus on one or two rules at a time.
Add a make target
On this project I am using Make
as a build tool.
Here is the Make
target I created to wrap validation:
vale: ## run linting rules against markdown files
vale README.md CONTRIBUTING.md # we don't valedate CHANGELOG.md as that reflects historical commit summaries
Now I can run make vale
to run that validation and use the vale
target as a prerequisite in my cit
(CI build and Test) target to fail a build if any of the markdown validation rules fail.
cit: clean vale build test-unit test-features
Once I get PR builds working on this repo that vale
feedback will be available to all contributors who make markdown changes.
Conclusion
Once I got over the initial setup challenges I found vale
very easy to use.
Similar with other code quality and linting tools I’ve used (I’m looking at you rubocop
) the first time I turned vale
on my project’s README.md
it was easy to get overwhelmed with the amount of feedback.
Now that I’ve worked through it however and having a passing mark on both README and CONTRIBUTING I find it easy to keep that validation on and deal with smaller more targeted feedback on only the content I’m changing.
A number of the initial pieces of feedback were challenging to resolve but eliminating passive voice really does make the content stronger.
I found that with the accept.txt
file to expand my spelling dictionary and a few targeted markdown comment when a rule truely didn’t make sense I am now very happy to have vale
running (and passing) against my project’s README.md
and CONTRIBUTING.md
files.