#+HTML_HEAD: <link rel="stylesheet" href="../static/style.css">
#+HTML_HEAD: <link rel="icon" href="../static/favicon.png" type="image/png">
#+EXPORT_FILE_NAME: index
#+TITLE: Ara
Ara is a simple cli program that prints Covid-19 stats. Currently it
only prints India's Covid stats.
| Project Home | [[https://andinus.nand.sh/ara/][Ara]] |
| Source Code | [[https://git.tilde.institute/andinus/ara/][Andinus / Ara]] |
| GitHub (Mirror) | [[https://github.com/andinus/ara/][Ara - GitHub]] |
*Tested on*:
- OpenBSD 6.7
- Perl v5.30
*Note* (OpenBSD users): If you're using a custom Perl install then add the
path to =OpenBSD::= in @INC.
| Demo Videos |
|----------------|
| [[https://diode.zone/videos/watch/95868534-8aae-497b-806e-5766236bb058][Ara 2020-06-14]] |
| [[https://diode.zone/videos/watch/03be044d-6ab7-4f01-8769-0084674dec93][Ara 2020-06-06]] |
* Documentation
=ara= by default will first look for the file in =$XDG_CACHE_HOME=, if that
is not set then =HOME/.cache= is used, the file name is assumed to be
=ara.json=, there is currently no option to override this or the full
path.
If you run =ara= on OpenBSD then it should use =OpenBSD::Unveil=.
Default logic is to check if the file is available locally & if it's not
older than 8 minutes, in any other case it fetches the latest data. This
can be controlled with =local= & =latest= option.
The file is downloaded over a secure connection & the server's identity
is verified.
** Options
*** local
This option forces =ara= to use the data available locally, it will only
override this option when the file doesn't exist on disk.
*** latest
This will force =ara= to fetch the latest data.
*Note*: =local= & =latest= option cannot be used together, =ara= will print a
warning & latest option will be ignored.
*** notes
Only state notes will be printed if this option is passed.
*** rows
=rows= option takes an integer as argument which can be passed as =--rows
n=, where =n= is an integer.
=ara= will only print maximum these many number of rows, if you pass 0 or
a negative number then =ara= will ignore it & print all the rows.
*** nodelta
This will remove delta values from every column.
*** nototal
This will remove the "Total" or "India" row from the table.
=hide= option should be used for this purpose, this option is only kept
for backwards compatibility.
*** nowords
"Confirmed", "Recovered" & "Deaths" column format numbers in words. For
example, "1.6 lakhs" instead of "1,60,000" which makes it easier to
read. This option will disable this behaviour.
"Active" column doesn't format numbers in words because it's alignment
is set as "right" & formatting it this way doesn't look good. There is
currently no option to change this behaviour.
*** hide
=hide= is able to hide states & columns from the table, the values should
be space seperated like =--hide active "last updated" recovered=. These
are case sensitive & should be lowercase.
Arguments can be passed as they're printed, for example =--hide "jammu
and kashmir"= is equivalent to =--hide jk= because "JK" is what's printed
on the table.
Only "States" & "Notes" column cannot be hidden, =ara= will print a
warning if you try to do so.
*Note*: "updated" is aliased to "last updated", so you can pass =--hide
updated= & it would hide the "last updated" column.
*Note*: The feature to get space seperated values is marked as
experimental in =Getopt::Long= so the behaviour can change in future,
worse even get removed. To guarantee backwards compatibility pass each
value by itself like =--hide jk --hide active=, this is equivalent to
=--hide jk active=.
**** Implementation
=%hide= hash is created from =@to_hide= which was created from user
arguments by =Getopt::Long=.
#+BEGIN_SRC perl
undef @hide{ @to_hide }
if scalar @to_hide;
#+END_SRC
=%hide= contains values of =@to_hide= as keys & the value to those keys is
not defined, hence =undef=. This one line says Perl to "undef these keys
from the hash =%hide=" where these refers to the values of =@to_hide=. This
will fail if =@to_hide= is empty so we have to check for that.
Alternatively we can do =@hide { @to_hide } = ()= which works even if
=@to_hide= is empty & does the same thing otherwise, this looks more
cryptic so I use the first way.
To check if a specific column is to be hidden or not we use =exists= like
=exists $hide{something}=.
There are other ways of doing this & maybe those would be better, I
didn't test which one was the best.
***** Columns
To make =hide= work we put create =@columns= & push columns to it unless the
user has asked to hide it.
#+BEGIN_SRC perl
my @columns;
push @columns, 'Confirmed' unless exists $hide{confirmed};
push @columns, 'Active' unless exists $hide{active};
#+END_SRC
***** States
The whole block is skipped if the user has asked to hide the state. As
said above, statecode is also check if that's what is printed in the
table which is true only if =length $state > 16=. There is no good reason
for not checking statecode for everything.
#+BEGIN_SRC perl
next
if exists $hide{lc $state}
# User sees the statecode if length $state > 16 so we also match
# against that.
or ( length $state > 16
and exists $hide{lc $statewise->[$i]{statecode}});
#+END_SRC
*** show
=show= also accepts space seperated values & just like in =hide='s case it's
experimental & can change in future.
=show= will only show states that are passed. For example, =--show jk= will
only print data for Jammu & Kashmir. If both =show= & =hide= is used for
states then =hide= is ignored. =show= for states can be used with =hide= for
columns.
**** Implementation
=show='s implementation is similar to =hide='s. =%show= hash is created from
=@to_show=.
#+BEGIN_SRC perl
undef @show{ @to_show }
if scalar @to_show;
#+END_SRC
If user has used =show= then =hide= is ignored, this is achieved by an
if-else block. This also means that invalid values in state would cause
=hide= to be ignored, for example passing =--show invalid= wouldn't match
anything but =hide= will still be ignored. This is intentional.
#+BEGIN_SRC perl
if ( scalar @to_show ) {
next
unless exists $show{lc $state}
or ( length $state > 16
and exists $show{lc $statewise->[$i]{statecode}});
} else { ... }
#+END_SRC
*** help
=help= will print help for =ara= which will have little information about
all these options listed above.
** Cross-platform compatibility
Previously =ara= had OpenBSD specific code & would simply fail to run on
other OSes, now it runs on all platforms. There is still OpenBSD
specific code but it's used only when =ara= detects to be running on
OpenBSD.
#+BEGIN_SRC perl
use constant is_OpenBSD => $^O eq "openbsd";
require OpenBSD::Unveil
if is_OpenBSD;
sub unveil {
if (is_OpenBSD) {
return OpenBSD::Unveil::unveil(@_);
} else {
return 1;
}
}
#+END_SRC
=is_OpenBSD= is a constant so the if-else block is optimized at compile
time. Another way would be to define the sub inside the if-else block
which is what I did initially but that is not the same thing as this.
You cannot define sub like that in Perl because this step happens at
compile time & so the if-else block is ignored, which means the code
will be equivalent to else block being true all the time because that's
what comes later.
#+BEGIN_SRC perl
if (is_OpenBSD) {
require OpenBSD::Unveil;
OpenBSD::Unveil->import;
} else {
sub unveil { return 1; }
}
#+END_SRC
Above code block will override the unveil sub to be =return 1;= everytime,
this was fixed in commit =245aebe3da915afc0feafc7257f025e2e66a987f=.
This will still fail on OpenBSD if users don't have =OpenBSD::Unveil= in
=@INC=, this shouldn't be an issue with Perl in base but if user runs
custom Perl then it might not be in =@INC=, in that case user is expected
to fix this by adding the path to =OpenBSD::= in =@INC=.