About fn
fn
is a function-oriented general purpose automation tool that aims to be simpler and more flexible than similar tools such as Make, Task, and Rake.
fn
is a commandline program that runs functions
(shortened to fns
) defined in a fnfile.yml
, similar to how you use make
to run targets in a Makefile
.
Design Philosophy
fn
aims to have a human-centered design, with emphasis on usability and aesthetics.
Even the smartest among us can feel inept as we fail to figure out which light switch or oven burner to turn on, or whether to push, pull, or slide a door. The fault lies not in ourselves, but in product design that ignores the needs of users and the principles of cognitive psychology.
The problems range from ambiguous and hidden controls to arbitrary relationships between controls and functions, coupled with a lack of feedback or other assistance and unreasonable demands on memorization.
The rules are simple: make things visible, exploit natural relationships that couple function and control, and make intelligent use of constraints.
The goal: guide the user effortlessly to the right action on the right control at the right time.
-- The Design of Everyday Things
What is a function?
Functions are "self-contained" modules of code that accomplish a specific task. Functions usually "take in" data, process it, and "return" a result. Once a function is written, it can be used over and over and over again. Functions can be "called" from the inside of other functions.
The very basic concept of "taking in" data and "returning a result" is lost by other tools, and indeed, I found myself constantly wanting to do this exact thing all the time.
Basics
An example to get you started:
version: '0.1'
fns:
hello: echo "Hello, World!"
❯ fn hello
Hello, World
Understanding the fnfile
The top-level fns
is a map of defined functions. With the key being the name of the fn
, and the value being one of several types, allowing you to express intent without requiring excessive syntax.
fn
- Function Definition
The common fields of an fn
are:
do
- Thedo
step. required**dir
- The working directory when running shell steps. The default is same directory as thefnfile.yml
.
Common Steps
do
Do
performs the given steps serially.
do:
steps: [ <STEP> ]
parallel
Parallel
performs the given steps in parallel.
parallel:
steps: [ <STEP> ]
sh
Sh
runs a shell command.
sh:
run: <string>
Flexible Nature
The following tasks are all the same. The resolve to a single sh
step, which runs a shell command.
fns:
hello: echo "hello"
foo:
- echo "foo"
bar:
do: echo "bar"
baz:
do:
- echo "baz"
biz:
do:
- sh: echo "biz"
qux:
do:
steps:
- echo "qux"
ipsum:
do:
steps:
- sh: echo "ipsum"
amet:
do:
steps:
- sh:
- run: echo "amet"
Why is this important?
The design of fn
is to get out of your way as quickly as possible. Meaning if you don't need certain functionality, you don't need to declare it or even make room for it.
Let's take a fn
as an example of how we can some liberties without affecting functionality, requiring you to write a bunch of code, or affecting readability.
The definition of a fn
is that of a function definition, declaring among other things, what steps should take place. fn
gives you a do
as your "entrypoint" to declaring those steps.
fns:
my-function:
do:
steps:
- sh:
run: echo "hello"
Here we are declaring a do
with 1 step, that of an sh
step when runs the shell command: echo "hello"
.
The first shortcut, is that for sh
.
The definition of a sh
step:
# VALID
sh:
run: <string>
dir: <string, optional>
Given that run
is the only required field, you can shortcut this by simply using:
sh: <string>
Furthermore, since sh
is very likely the most common thing you will likely be writing (since it is the step type that actually executes work), in many cases, we can shortcut this entirely to just:
<string>
So the first step is to reduce sh
to just sh: <string>
fns:
my-function:
do:
steps:
+ - sh: echo "hello"
- - sh:
- run: echo "hello"
Furthermore, fn
makes an assumption, that if it sees just a string in a place that would accept a step
, that string is assumed to be a sh
step:
fns:
my-function:
do:
steps:
+ - echo "hello"
- - sh: echo "hello"
But there's a few more optimizations to make. For one, you aren't using any other functionality of do
, just the steps
field. The steps
field is also the most important part of a do
step. So we can shortcut the entire do
into just an array, and not require you explicitly declare steps
:
fns:
my-function:
do:
+ - echo "hello"
- steps:
- - echo "hello"
Furthermore, if you have only 1 step, why should I force you to use an array?
fns:
my-function:
+ do: echo "hello"
- do:
- - echo "hello"
We can apply the same logic one more time, to declare an entire fn
definition as that of a single sh
step, with the following:
fns:
+ my-function: echo "hello"
- my-function:
- do: echo "hello"
So for the simplest of things, there's really not much to write.