loading...

Markdown-based task runner - 'saku'

kt3k profile image Yoshiya Hinosawa ・4 min read

tl;dr

  • It's painful to use make command as a task runner. It's especially intolerable when writing .PHONY directives for evey task.
  • I found some simple altenatives (like go-task, robo), but I didn't like them.
  • I came up with the idea that markdown seems a better syntax for task definition.
  • I made a markdown-based task runner, named saku!

Motivation

Because I've been using JavaScript both at work and hobby always, I use npm run-script as my task runner. run-script has some flaws such that it's unable to be documented well in a decent way, or that it's a little hard to read because it's written in JSON file. But in my opinion, it's sufficient tool for defining the necessary tasks with the support of the tools like npm-run-all, cross-env etc.

One day I started using terraform for our team's infrastructure management and I was setting up the git repository for it.

I was searching the web for how to set up the terraform project and many people seem using Makefile for organizing the commands in it.

I thought it was a good idea to use task runner to invoke terraform because terraform is too long to type, but I also felt uncomfortable about using Makefile in 2018. It's depressing to write enigmatic .PHONY directive again and again. In the first place make isn't a tool for running tasks, but a tool for compiling source code of the language like C, C++ etc. It is a hack to use make as a task runner and I thought there must be some other tools which are more appropriate for just running tasks in 2018.

go-task

I found several tools. Among them, go-task seemed the closest to what I was looking for. go-task is a simple tool. It uses the yaml format for task configuration.

However I felt something was wrong about it. That was the key names in the yaml DSL. In go-task, you write commands and descripions as values of keys cmds and desc.

tasks:
  build:
    cmds:
      - go build -v -i main.go

  assets:
    cmds:
      - minify -o public/style.css src/css

To my eyes, these key names look a little redundant and seem noisy. In Makefile, targets are expressed as their names and colons and the commands are expressed by tab indents.

build:
    go build -v -i main.go

assets:
    minify -o public/style.css src/css

That was more elegant. Because I'm looking for more elegant replacement of make, it's so unfortunate if the notations get less elegant.

Think syntax

I considered whether it's possible or not to build more simpler version of configuration in yaml syntax, but I gave up because it seemed impossible because of the characteristics of yaml. In yaml, if you need to define something, you always need key: value pair for anything and I felt it's impossible to get a simpler version than go-task's design.

Then I thought about several languages which I was familiar with and finally I found markdown is very much suitable for my needs because it allows bare strings in many places in different contexts.

Markdown-based task runner

I couldn't find markdown-based task runner and therefore I created one. I named it saku!

I used remark as markdown parser and used minimist as CLI option parser. I reused several toolchains for building CLI base structure which I had prepared for writing some other tools like kocha or bulbo. I stole some techniques from yarn to show emojis only in TTY environment.

I modeled the domain of saku according to what DDD book teaches me. I like this style of progarmming these days quite a lot. Modeling makes the software development quite straightforward and easy for reasoning and testing. In the course of writing suku, I created the only one model class Task and the entire implementation seemed very organized ☺️.

πŸ• Dogfooding

I started dogfooding it at 12th commit by writing down the tasks in saku.md which are necessary to build saku itself.

# test
> Runs unit tests

    npx kocha --require src/__tests__/helper src{/,/**/}__tests__/*.js

# lint
> Runs lint checks

    npx standard

# fix
> Runs auto fixer of lint tool

    npx standard --fix

# cov
> Makes coverage reports

    npx nyc --reporter=text-summary --reporter=lcov saku test

# codecov
> Posts reports to codecov.io

    saku cov
    npx codecov

As I expected, I felt it was easy to write, and unexpectedly I found that the rendering of the saku.md in github UI was so beautiful).

Wrap up

  • It's so fun to create things from scratch! πŸ˜„
  • Though markdown doesn't have a natural logical structure like yaml, personally I think it has a great potential as DSL because it has various places where bare strings can be written without quotes.

Update: 2018-05-12

I've completely rewritten saku in go language. Please check this version as well πŸ˜‰

Posted on by:

kt3k profile

Yoshiya Hinosawa

@kt3k

A webdev, loves JavaScript, recently contributing to Deno.

Discussion

pic
Editor guide
 

Interesting approach! On the one hand, I love the expressiveness and human-readability of the task runner definition. On the other hand, I'm slightly irked by overloading markdown syntax with different meaning: first-level header is task name, so what is second-level header?

 

Thanks for the comment!

I admit it's a little unnatural to interpret markdown in this way. Probably this approach only works for very simple use cases.
But IMV task definition is simple enough thing which fit into the subset of markdown semantics. So I choose it for config language.

BTW all levels of headings have the same effect as the first-level!

 

Doesn't make senses that a second-level inside a first one do the same think that the first-level do and something more?

saku doesn't do anything special for the levels of headings, but the user can use the different levels of headings for expressing, for example, different significances of tasks.

For example,

# build

    saku build:js build:css

## build:js

    browserify blah blah

## build:css

    sass blah blah

Where build is at the 1st level because it's an important task, and build:js and build:css are at the 2nd level because they are less important.

 

Interesting idea. Though I found this method allows you to do just a simple things. No more.

So far it's just 2 task runner posts in dev.to under "taskrunner" tag, yours and mine (: