Create Badges with GitHub Actions!
Everybody loves badges. With GitHub actions you can even create dynamic badges which change with every commit. In this guide I will show you how!
The Goal
For the README.md of Fly-Pie’s GitHub repository I wanted to have badges which show the current lines of code and the percentage of comment lines. These numbers can change very frequently, so I needed a system for dynamic badges without polluting my git history. And here is the result:
These badges will always show the current numbers and can be embedded everywhere, also in the README.md
of GitHub!
In this post I will show you how these badges are created.
1. Calculate the Lines of Code
First you will need a script which calculates the number of code lines and comment lines for your project.
I have created the script below which you may use as a starting point for your project.
It uses the awesome cloc
commandline tool, hence it can be adapted for almost all programming languages.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#!/bin/bash
# This scripts counts the lines of code and comments in all source files
# and prints the results to the command line. It uses the commandline tool
# "cloc". You can either pass --loc, --comments or --percentage to show the
# respective values only.
# Some parts below need to be adapted to your project!
# Get the location of this script.
SCRIPT_DIR="$( cd "$( dirname "$0" )" && pwd )"
# Run cloc - this counts code lines, blank lines and comment lines
# for the specified languages. You will need to change this accordingly.
# For C++, you could use "C++,C/C++ Header" for example.
# We are only interested in the summary, therefore the tail -1
SUMMARY="$(cloc "${SCRIPT_DIR}" --include-lang="JavaScript" --md | tail -1)"
# The $SUMMARY is one line of a markdown table and looks like this:
# SUM:|101|3123|2238|10783
# We use the following command to split it into an array.
IFS='|' read -r -a TOKENS <<< "$SUMMARY"
# Store the individual tokens for better readability.
NUMBER_OF_FILES=${TOKENS[1]}
COMMENT_LINES=${TOKENS[3]}
LINES_OF_CODE=${TOKENS[4]}
# To make the estimate of commented lines more accurate, we have to
# subtract any copyright header which is included in each file.
# For Fly-Pie, this header has the length of five lines.
# All dumb comments like those /////////// or those // ------------
# are also subtracted. As cloc does not count inline comments,
# the overall estimate should be rather conservative.
# Change the lines below according to your project.
DUMB_COMMENTS="$(grep -r -E '//////|// -----' "${SCRIPT_DIR}" | wc -l)"
COMMENT_LINES=$(($COMMENT_LINES - 5 * $NUMBER_OF_FILES - $DUMB_COMMENTS))
# Print all results if no arguments are given.
if [[ $# -eq 0 ]] ; then
awk -v a=$LINES_OF_CODE \
'BEGIN {printf "Lines of source code: %6.1fk\n", a/1000}'
awk -v a=$COMMENT_LINES \
'BEGIN {printf "Lines of comments: %6.1fk\n", a/1000}'
awk -v a=$COMMENT_LINES -v b=$LINES_OF_CODE \
'BEGIN {printf "Comment Percentage: %6.1f%\n", 100*a/(a+b)}'
exit 0
fi
# Show lines of code if --loc is given.
if [[ $* == *--loc* ]]
then
awk -v a=$LINES_OF_CODE \
'BEGIN {printf "%.1fk\n", a/1000}'
fi
# Show lines of comments if --comments is given.
if [[ $* == *--comments* ]]
then
awk -v a=$COMMENT_LINES \
'BEGIN {printf "%.1fk\n", a/1000}'
fi
# Show precentage of comments if --percentage is given.
if [[ $* == *--percentage* ]]
then
awk -v a=$COMMENT_LINES -v b=$LINES_OF_CODE \
'BEGIN {printf "%.1f\n", 100*a/(a+b)}'
fi
Save this as cloc.sh
in the root directory of your repository and make it executable:
Then, when you execute the script, it will show you the number of code lines, comment lines and the percentage of comment lines.
2. Use the Dynamic Badges Action
Next you have to setup a workflow which creates badges based on these numbers. To allow this, I have created a GitHub Action which can be used to create arbitrary badges for your README.md from within a GitHub Actions Workflow. This action creates a JSON description of the badge and uploads it to a gist. This JSON description can then be used with shields.io/endpoint to create the final badge.
Please read the configuration instructions of the Dynamic Badges Action. You will have to create a public gist and add a secret to your repository allowing the action to push to this gist.
Below you will find an exemplary workflow which will execute the cloc.sh
script on each commit to the main
branch and update the badges accordingly.
This workflow has one job with five steps.
- Checkout the repository.
- Download the
cloc
tool. - Execute the
cloc.sh
script twice to get the required numbers. - Execute the Dynamic Badges Action once to upload the JSON description of the lines-of-code badge.
- Execute the Dynamic Badges Action once more to upload the JSON description of the comment-percentage badge.
You will have to replace <gist-ID>
by the ID of the gist you created earlier.
The badge showing the lines of code will have a fixed lightgrey
color, the comment-percentage badge will have an automatic color: It will be red for 0% comments and green for 50% comments.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
name: Badges
on:
push:
branches:
- main
jobs:
update-badges:
name: Update Badges
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v2
- name: Download cloc
run: sudo apt-get update -y && sudo apt-get install -y cloc
- name: Get the Numbers
run: |
echo "CODE_LINES=$( ./cloc.sh --loc)" >> $GITHUB_ENV
echo "COMMENT_PERCENTAGE=$(./cloc.sh --percentage)" >> $GITHUB_ENV
- name: Create Lines-of-Code-Badge
uses: schneegans/dynamic-badges-action@v1.3.0
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: <gist-ID>
filename: loc.json
label: Lines of Code
message: ${{ env.CODE_LINES }}
color: lightgrey
- name: Create Comments-Badge
uses: schneegans/dynamic-badges-action@v1.3.0
with:
auth: ${{ secrets.GIST_SECRET }}
gistID: <gist-ID>
filename: comments.json
label: Comments
message: ${{ env.COMMENT_PERCENTAGE }}%
valColorRange: ${{ env.COMMENT_PERCENTAGE }}
maxColorRange: 50
minColorRange: 0
3. Embed the Badges
Once this workflow has run for the first time, you will find new files (loc.json
and comments.json
in the example above) in the gist you created before.
Embedding the badges is now straight-forward.
Shields.io caches the these JSON files for at least 300 seconds, so it may take up to five minutes for your badges to refresh when you re-execute the workflow.
Final Thoughts
Of course, this cannot be used for lines-of-code badges only. In fact you can create badges for basically anything which may change from commit to commit! Let me know if you find another cool use case!