PicoLisp Explored: Piping from the Command Line
Tags: picolisp, functional, tutorial
Published: 2021-11-27 10:31:10

Today we will explore further input/output options in PicoLisp, specifically how to **pipe input from the command line to a PicoLisp script**. For the example, we will try to read the **battery status of our laptop**, but of course the principles apply to any kind of command line output.

This article builds up on the [Input-Output functions](https://picolisp-blog.hashnode.dev/60-picolisp-functions-you-should-know-3-inputoutput)
post from the Beginner's Guide.

---------------------------------

### Getting the battery status (Debian)

In Debian systems we can get the status of your system with the ``upower`` command. First, we need to find the path of your power source by enumerating our system with ``upower -e``:

```
$ upower -e
/org/freedesktop/UPower/devices/line_power_ACAD
/org/freedesktop/UPower/devices/battery_BAT1
/org/freedesktop/UPower/devices/mouse_hidpp_battery_0
/org/freedesktop/UPower/devices/DisplayDevice
``` 
*``$`` stands for commands which are executed in the terminal.*

We can see a number of battery powered devices. The main one which powers my laptop is ``battery_BAT1``.

Now we let's check the status of "battery_BAT1" by typing ``upower -i <path>`` which gives a lot of information such as vendor, path, model...:

```
$ upower -i /org/freedesktop/UPower/devices/battery_BAT1
  native-path:          BAT1
  [...]
  battery
    [...]
    state:               discharging
    warning-level:       none
    energy:              23,1186 Wh
    energy-empty:        0 Wh
    energy-full:         49,7364 Wh
    energy-full-design:  55,2372 Wh
    energy-rate:         13,3242 W
    voltage:             15,018 V
    time to empty:       1,7 hours
    percentage:          46%
    capacity:            90,0415%
    technology:          lithium-ion
    [...]
```

Let's try to pipe this output to a simple PicoLisp script.

----------------------------

### Creating the script

As we learned in the "Input-Output"-functions blog, we have two main options to read in from an input source:
- ``read`` stops at specific delimitors such as whitespace or new-line,
- ``line`` reads in the full line.  

But what is our input source? Let's check the documentation of the ``in``-function:

> ``(in 'any . prg) -> any``

>    Opens any as input channel during the execution of prg. The current input channel will be saved and restored appropriately. If the argument is NIL, standard input is used. If the argument is a symbol, it is used as a file name (opened for reading and writing if the first character is "+"). [...] Otherwise (if it is a list), it is taken as a command with arguments, and a pipe is opened for input. [...]

According to the docs, we have three main options: 
1. Reading from standard input using ``in NIL``
2. Reading from file using ``in <filename>``
3. Reading from command with ``in <list>``.

Let's try the third option and see if we can get the output to PicoLisp. It seems we need to **transform the command to a list**. What does that mean? 

It's much easier than you might expect - it simply means we split up the words of the command like this: ``("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1")``. 

Let's type ``pil +`` to open the PicoLisp REPL and try to get the first line:

```
: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (line T))      
-> "  native-path:          BAT1"
```
*Note: the quote ``'`` before the list is needed to escape evaluation. ``(line T)`` returns the list as single string instead of a list of single characters.*

We get the first line of the command line printed as expected.

--------------------------------------

Now let's do the same using [**piping**](https://www.geeksforgeeks.org/piping-in-unix-or-linux/). In this case, we use input option 1) ``NIL`` to get the input from "**the current input channel**". We define a script called ``battery.l`` and try:

```
# <battery.l>
(in NIL
   (prinl (line)) )

(bye)
```
Now we query the battery status from the command line and pipe it to the PicoLisp script.

```
$ upower -i /org/freedesktop/UPower/devices/battery_BAT1 | pil battery.l
  native-path:          BAT1
```
Again, we receive the first line as output. 

-------------------

### Formatting the output

``upower`` gives  us a lot of information, but probably we will not need all of it. Of course we could pre-format output using tools like [grep](https://en.wikipedia.org/wiki/Grep), but let's try to do it in PicoLisp now.

Some helpful functions:
- ``(from 'any)``: Skips the current input channel until one of the strings ``any`` is found, and starts subsequent reading from that point.
- ``(till 'any) ``: Reads from the current input channel till a character contained in ``any`` is found (or until end of file if any is ``NIL``).
-``skip ['any]``: Skips all whitespace (and comments if ``any`` is given) in the input stream.

As an example, let's say we want to read out the battery status and percentage. As a reminder, the command line output has this format:

```
    state:               discharging
    percentage:          46%
```
Let's add ``(from "percentage:")`` to our previous script.  

```
: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (from "percentage:") (line T]
-> "          70%"
```
*``]`` is a short form which closes all open parentheses.*

The output is correct, but we have a lot of whitespace before the output. In order to prettify it a  little bit, let's ``(skip)`` the whitespace:
 
```
: (in '("upower" "-i" "/org/freedesktop/UPower/devices/battery_BAT1") (from "percentage:") (skip) (line T]
-> "70%"
```
Looks good!

---------------------------

### Finalizing the script

We are almost done, but let's make three small improvements:
1. We should add a check if the ``"percentage:"`` string exists in our piped output before we try reading it. 
2. Read the output of ``"state"``
3. Add some custom formatting to the output.

A possible solution could look like this:

```
(in NIL
   (when (from "state:")
      (skip)
      (prinl "Battery status: " (line))
      (when (from "percentage:")
         (skip)
         (prinl (line) " remaining") ) ) )

(bye)
```
Calling the script returns:

```
$ upower -i /org/freedesktop/UPower/devices/battery_BAT1 | pil batt.l
Battery status: charging
49% remaining
```

The final script can be downloaded [here](https://gitlab.com/picolisp-blog/single-plage-scripts/-/blob/main/picolisp-explored/batt.l).

----------------------------

### Sources

- http://manpages.ubuntu.com/manpages/trusty/man1/upower.1.html  
- https://askubuntu.com/questions/69556/how-do-i-check-the-batterys-status-via-the-terminal  
https://software-lab.de/doc/index.html