Tutorial to ramda-cli, a jq-like JSON processor for command-line
In this tutorial we'll use ramda-cli with GitHub's Repos API to get a list of @jeresig's most starred repos.
ramda-cli is a command-line tool for processing JSON using functional pipelines. As the name suggests, its utility comes from Ramda and the wide array of functions it provides for operating on lists and collections of objects. It also employs LiveScript for its terse and powerful syntax.
On the way, there's a gentle introduction to some functional programming
concepts such as currying and function composition. We'll build a pipeline of
functions that takes a list of repos and returns the ten most starred repos in
descending order as a list of {name, stargazers_count} objects. Finally, we'll
print the result as table.
Copy-pasting this into a shell session will make all the examples runnable:
url=https://api.github.com/users/jeresig/repos\?per_page\=100
#fetch the data with curl
Let's first use curl to get the list of repos in JSON format and pipe it to
R identity -p to get an idea of what we're working with.
|
As in programming, in ramda-cli data is manipulated by applying a function to the data. The result will by default be written to standard output in JSON format.
Since identity stands for a function that
simply returns its argument, our command will pipe the JSON payload unchanged
through to stdout in a more readable (-p is for pretty) format.
Reader exercise: Replace
identitywith a function that would return the number of repos.
#pluck the names
@jeresig has a lot of repos and the API returns a ton of info we don't care about, so we'll go ahead and see how the output could be reduced to just a list of the names of those repos.
|
For those unfamiliar with Ramda's curried API or LiveScript, this will require some explaining.
In LiveScript, as in CoffeeScript, parentheses are optional when calling a
function. Backslash preceding a word is sugar for string. Therefore,
pluck \name compiles into pluck('name') in JavaScript.
pluck :: String -> {*} -> [*]Returns a new list by plucking the same named property off all objects in the list supplied.
pluck is a function that for a given key and
a list of objects, returns a list of values corresponding to that key from all
the objects in the list. Ramda's functions are all by design
curried, so we can partially apply pluck with just the key
we want, 'name', and thus get back a function that will be waiting for the
second argument, a list of objects.
Since curl gives us a list of repos, it's a great match for a function that is waiting for a list objects to get the properties from.
#filter out forks
Looks like the output contains repos that are forks. Not a big deal but for the sake of example we could get just the repos that are originally by @jeresig.
|
Here, where-eq set up a with a spec object
({ fork: true }) creates a predicate function to be used with
filter. filter is now waiting for the
second argument that curl will provide, a list of repos to filter.
Notice that two independent pieces of code are now passed to R. What happens
here is our program is still evaluates into a single function, but it's composed
under the hood by ramda-cli from the given functions in order from left to
right. Therefore, what we just did is equivalent to explicitly using
R.pipe for function composition:
|
The list is first filtered, then name property is plucked from each object. In
this way we can build a pipeline of operations to be applied on our data in a
specific order.
#get the stars also
Now that we have a list of repo names that are not forks, we can make the output more interesting by grabbing also the number of stars.
Instead of pluck we need an operation that picks specific fields from a list
of objects. In Ramda, there's a function called
project for just that.
project :: [k] → [{k: v}] → [{k: v}]Reasonable analog to SQL select statement.
|
#sort by stars
Before we can make the output more visually appealing, we have a few steps to
add in our pipeline. First, sorting by stargazers_count in descending order.
sortBy :: (a → String) → [a] → [a]Sorts the list according to a key generated by the supplied function.
sortBy together with
prop sorts a list of objects according to the
field given to prop.
|
Finally, we apply reverse to get the most
starred projects first and limit the list to first 10 items with
take.
|
#render as table
Good, now that the data is getting transformed into a shape that has the info we want, it can be presented in a more readable format.
Using the --output-type table option, a list of objects may be printed as a
table in such way that the objects' keys become the table headers. It's
convenient because all we need is an uniform list of objects to get a pretty
table. So we'll do just that. Remove the -p flag from before and slap
-o table at the end.
|
┌────────────────────────┬──────────────────┐
│ name │ stargazers_count │
├────────────────────────┼──────────────────┤
│ processing-js │ 1684 │
├────────────────────────┼──────────────────┤
│ node-stream-playground │ 311 │
├────────────────────────┼──────────────────┤
│ fireunit │ 228 │
├────────────────────────┼──────────────────┤
│ env-js │ 205 │
├────────────────────────┼──────────────────┤
│ trie-js │ 172 │
├────────────────────────┼──────────────────┤
│ pulley │ 171 │
├────────────────────────┼──────────────────┤
│ retweet │ 72 │
├────────────────────────┼──────────────────┤
│ dromaeo │ 63 │
├────────────────────────┼──────────────────┤
│ stack-scraper │ 46 │
├────────────────────────┼──────────────────┤
│ jquery-workshop │ 38 │
└────────────────────────┴──────────────────┘
Reader exercise: Add a URL column so that the projects can be viewed in browser.
This concludes the tutorial. If you're new to Ramda and want to learn more, check out the list of articles in the wiki. For ramda-cli, the README provides helpful information and examples.
Thanks to buzzdecafe for providing feedback on this article.
#bonus section: run from a file
As the pipeline grows, it becomes increasingly more manageable option to write
the pipeline in a separate script file. For this, ramda-cli provides the
--file option:
-f, --file String read a function from a js/ls file instead of args; useful for
larger scripts
// most-starred.js
;
;
module.exports =
isNotFork,
,
'stargazers_count',
R.reverse,
10,
;
|