A concise mtime sorted directory listing application
Tags: perl, mojolicious, listing, mtime
Published: 2020-02-18 19:10:16

Today we will focus on a simple task: listing the files contained in a directory, sort them by modification time ([`mtime`](https://en.wikipedia.org/wiki/Stat_(system_call)#ctime)) and display the result in a JSON array.

We are gonna use [`Mojo::File`](https://mojolicious.org/perldoc/Mojo/File) for the file system part and [`Mojolicious::Lite`](https://mojolicious.org/perldoc/Mojolicious/Lite) to expose those data on a simple but effective JSON API. 

# Prerequisites 

```shell
apt install libmojolicious-perl
    
touch mtime.pl && chmod +x mtime.pl
```

# The code

Our lite application is separated in two main parts:

- a [*helper*](https://mojolicious.org/perldoc/Mojolicious/Guides/Tutorial#toc), the `get_paths_by_ctime()` method
- an unique [*route*](https://mojolicious.org/perldoc/Mojolicious/Guides/Tutorial#toc) that returns an array reference containing our file list

```perl
#!/usr/bin/env perl

# Automagically import Mojo::Base -base
use Mojolicious::Lite -signatures;
use Mojo::File 'path';

helper get_paths_by_mtime => sub {
  my $files_list = path( '/tmp' )->list;

  # Returns an array of Mojo::File
  my @files = sort { $a->stat->mtime <=> $b->stat->mtime }
    map { $_ } $files_list->each;

  # Returns an array of paths sorted by modification date
  return map { $_->realpath->to_string } @files;
};

get '/' => sub( $c ) {
  $c->render( json => \$c->app->get_paths_by_mtime );
};

app->start;
```

# Starting the app

Mojolicious comes with the `morbo` HTTP and WebSocket development server

    morbo mtime.pl
    Server available at http://127.0.0.1:3000

If you visit this adress in your browser, it should display the list of files contained in the `/tmp` directory of your computer. On mine it returns:

```json
[
  "/tmp/sddm-:0-RweLaZ",
  "/tmp/xauth-1000-_0",
  "/tmp/cpan_install_3QQI.txt",
  "/tmp/!home!sfeu!.emacs.d!savefile!ido.hist~",
  "/tmp/!home!sfeu!.emacs.d!savefile!recentf~",
  "/tmp/!home!sfeu!dev!processing-perl!etc!crontab~",
  "/tmp/!home!sfeu!.emacs.d!personal!custom.el~",
  "/tmp/!home!sfeu!dev!processing!.env~",
  "/tmp/liste_desabonnement_20200218.csv",
  "/tmp/!home!sfeu!dev!processing!.git!COMMIT_EDITMSG~",
  "/tmp/!home!sfeu!scripts!mtime.pl~"
]
```

It is just a bunch of temporary files, mostly those I edit in my text editor. Note that the directories and special files (`.`, `..`) are not listed.

# Releasing the power of `Mojo::File` 

## Retrieving a list of files

Mojolicious provide a lot of utilities that makes your Perl programmer life almost bearable. Note that Mojolicious needs zero dependencies, this is the reason it comes with a great toolkit for doing simple tasks. [`Mojo::File`](https://mojolicious.org/perldoc/Mojo/File) is one of those. Let's see how to use it.

```perl
use Mojo::File 'path';
```

By *using* `Mojo::File` and loading it's *path* function, you will be able to *construct a `Mojo::File` object* by passing it any kind of path:

```perl
my $files_list = path( '/tmp' )->list;
```

The `list` method called on a `Mojo::File` directory will return all files contained in this directory. The returned object is a [`Mojo::Collection`](https://mojolicious.org/perldoc/Mojo/Collection) object, an *array-based container for collections*.

## Sorting the objects by modification date

To read the following code, start by the end.

```perl
my @files = sort { 
  $a->stat->mtime <=> $b->stat->mtime 
} $files_list->each;
```

- first, you return all the elements of the collection by using `each`
- it will return an array, that can be passed to [`sort`](https://perldoc.pl/functions/sort)

`sort` is being passed a comparison function. In our case we will compare by `mtime` (modification time). The reason we can access to `mtime` from the array of `Mojo::File`s is that this module got it's own [`stat` method](https://mojolicious.org/perldoc/Mojo/File#stat) that returns itself *a [`File::stat`](https://mojolicious.org/perldoc/File/stat) object for the path*. This is where the magic happend!

## Retrieving the files paths

By using a simple `map` function and the `Mojo::File` chained`realpath->to_string` methods we will transform our array of objects to a simple array of strings:

```perl
return map { $_->realpath->to_string } @files;
```

# The route

The result of our helper is then *rendered* as JSON (must be passed to the front as an `arrayref`).

# Making it even more concise

[Marcus Ramberg](https://marcus.nordaaker.com/about) explained me how to make this even more concise by using [`Mojo::Collection->sort`](https://mojolicious.org/perldoc/Mojo/Collection#sort)

The helper can be rewritten:

```perl
helper get_paths_by_mtime => sub {
  return path( '/tmp' )->list
    ->sort( sub { $a->stat->mtime <=> $b->stat->mtime })
    ->map('realpath');
};

```

# Conclusion

The conciseness and readability of this program is quite surprising thanks to the effective tools used. 

I hope you enjoyed reading this presentation and that it gave you the wish to use the *formidable* Mojolicious framework and it's utilities.

# Acknowledgments

Thanks to Marcus Ramberg who read this, improved and simplified the code.