ifdef::env-github[]
= Pistol
:toc:
endif::[]
ifndef::env-github[]
// If processed locally, the README will be processed as a manpage
= pistol(1)
:doctype: manpage
:manmanual: Pistol
:mansource: Pistol
:man-linkstyle: pass:[blue R < >]


== Name

pistol - General purpose file previewer designed for Ranger, Lf to make scope.sh redundant.
endif::[]

== Introduction

ifdef::env-github[]

Pistol is a file previewer for command line file managers such as
https://ranger.github.io/[Ranger], https://github.com/gokcehan/lf[Lf] and
https://github.com/jarun/nnn[nnn], intended to replace the file previewer shell
script
https://github.com/ranger/ranger/blob/v1.9.2/ranger/data/scope.sh[`scope.sh`]
commonly used with Ranger and other previewing methods.

`scope.sh` is a Bash script that uses `case` switches and external
programs to decide how to preview every file it encounters. It knows how
to handle every file according to its
https://en.wikipedia.org/wiki/Media_type[MIME type] and/or file
extension using `case` switches and external programs. This design makes
it hard to configure / maintain and it makes it slow for startup and
heavy when running.

Pistol is a Go program (with (almost) 0 dependencies) and its MIME type
detection is internal. Moreover, it features native preview support for
almost any archive file and for text files along with syntax
highlighting while `scope.sh` relies on external programs to do these
basic tasks.

endif::[]
ifndef::env-github[]

Pistol features native preview support for almost any archive file and text
files along with syntax highlighting.

endif::[]

The following table lists Pistol’s native previewing support:

[cols=",",options="header",]
|===
|File/MIME Type |Notes on implementation
|`text/*` |Prints text files with syntax highlighting thanks to
https://github.com/alecthomas/chroma[`chroma`].

|Archives |Prints the contents of archive files using
https://github.com/mholt/archives[`archives`].
|===

In case Pistol encounters a MIME type it doesn't know how to handle
natively and you haven’t configured a program to handle it, it’ll print
a general description of the file type it encountered. For example, the
preview for an executable might be:

....
ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a34861a1ae5358dc1079bc239df9dfe4830a8403, for GNU/Linux 3.2.0, not stripped
....

This feature is available out of the box just like the previews for the
common mime types mentioned above.

ifdef::env-github[]
See also the WiKi article:
https://github.com/doronbehar/pistol/wiki/Pistol-out-in-the-wild[Pistol out in
the Wild].
endif::[]

ifdef::env-github[]

=== A Note on MIME type Detection

Some _pure_ Go libraries provide MIME type detection. Here are the top
search results I got using a common web search engine:

* https://github.com/gabriel-vasile/mimetype
* https://github.com/h2non/filetype
* https://github.com/rakyll/magicmime

Pistol uses the last one which leverages the well known C library
http://linux.die.net/man/3/libmagic[libmagic(3)]. I made this choice
after experimenting with the other candidates and due to their lack of
an extensive database such as
http://linux.die.net/man/3/libmagic[libmagic(3)] has, I chose
https://github.com/rakyll/magicmime[magicmime].

Note that this choice also features compatibility with the standard
command `file` which is available by default on most GNU/Linux
distributions.footnote:[Considering Pistol’s indirect dependence on
http://linux.die.net/man/3/libmagic[libmagic(3)], I will never take the
trouble to personally try and make it work on Windows natively. If
you’ll succeed in the heroic task of compiling `libmagic` for Windows and
teach https://github.com/rakyll/magicmime[magicmime] to use it, please
let me know.]

=== A Note on Archive Previews

Pistol previews all archive / compression formats supported by the Go
library https://github.com/mholt/archiver[`archiver`]. Some formats do
nothing but compression, meaning they operate on 1 file alone and some
files are a combination of archive, compressed in a certain algorithm.

For example, a `.gz` file is a _single_ file compressed with `gzip`. A
`.tar` file is an _uncompressed_ archive (collection) of files. A
`.tar.gz` is a `.tar` file compressed using `gzip`.

When pistol encounters a single file compressed using a known
compression algorithm, it doesn't know how to handle its content, it
displays the type of the archive. If a known compression algorithm has
compressed a `.tar` file, Pistol lists the files themselves.

https://en.wikipedia.org/wiki/Brotli[brotli] compressed archives,
(`.tar.br`) and Brotli compressed files (`.br`) are not detected by
`libmagic` so Pistol doesn't know how to handle them.footnote:[https://bugs.astron.com/view.php?id=111[`file` bug report];
https://github.com/google/brotli/issues/727[`brotli` bug report].]

== Install

If someone has packaged Pistol for your distribution, you might find a
package for of it linked
https://github.com/doronbehar/pistol/wiki/Distributions'-Packages[in the WiKi].

If not, use the following instructions, or grab a statically compiled version
of it from https://github.com/doronbehar/pistol/releases[the releases page],
available since version `0.3.1`. The releases assets include also the manual
page `pistol.1.gz`.

WARNING: Currently, Darwin compatible binaries are not available there, but it
should be possible to generate them via CI. All other binaries were cross
compiled from my own `x86_64` linux machine, using Nix' superb
https://nix.dev/tutorials/cross-compilation[cross compilation support].

NOTE: For a statically compiled Pistol to be functional, it needs to read a `libmagic` database
(usually found in `/usr/share/misc/magic.mgc`) and the static executable
includes the contents of this database found on https://github.com/NixOS/nixpkgs/blob/nixos-unstable/pkgs/tools/misc/file/default.nix[Nixpkgs'
`file`]
package. The content of the `magic.mgc` database is copied to
`~/.local/share/pistol/${libmagic-version}.mgc` when you first run the executable.
Hence using this executable might not be desirable due to this behaviour which
a regular compilation of `pistol` does not include. This behaviour is compiled
into `pistol` if you use `go {build,install}` with `-tags EMBED_MAGIC_DB`.


=== From Source

Since Pistol depends on https://github.com/rakyll/magicmime[magicmime],
you’ll need a `libmagic` package installed. Please refer to
https://github.com/rakyll/magicmime/tree/v0.1.0#prerequisites[this section in
Magicmime's README] for the appropriate commands for every OS. In particular,
if you installed `libmagic` using `brew` on a Darwin system, you may need to
use the `CGO_FLAGS` environment variable to compile `pistol`. See
https://github.com/doronbehar/pistol/issues/6#issuecomment-1576673173[#6] for
more details.

Assuming you've installed `libmagic` properly and you have
https://golang.org/doc/install[setup a Go environment], Use the
following command to install Pistol to `$GOPATH/bin/pistol`:

==== Go 1.16 or later

[source,sh]
----
go install github.com/doronbehar/pistol/cmd/pistol@latest
----

==== Go 1.15 or earlier

[source,sh]
----
env CGO_ENABLED=1 GO111MODULE=on go get -u github.com/doronbehar/pistol/cmd/pistol
----

footnote:[`env GO111MODULE=on` is needed due to a recent bug / issue
https://github.com/golang/go/issues/31529[with Go], see
https://github.com/doronbehar/pistol/issues/6[#6] for more details.
`CGO_ENABLED=1` is needed for https://github.com/rakyll/magicmime[magicmime],
see https://github.com/doronbehar/pistol/issues/76[#76].]

Besides `libmagic`,
https://repology.org/project/asciidoctor/information[`asciidoctor`] is also
recommended, in order to compile the README as a manpage and install it. NixOS
For instance, does it like this:

[source,nix]
----
  # ...
  nativeBuildInputs = [
    installShellFiles
    asciidoctor
  ];
  postInstall = ''
    asciidoctor -b manpage -d manpage README.adoc
    installManPage pistol.1
  '';
  # ...
----

https://github.com/NixOS/nixpkgs/blob/master/pkgs/tools/misc/pistol/default.nix[Link
to currently evaluated file on NixOS]. Packagers for other distros should do
something similar.


endif::[]

== Usage

....
$ pistol --help
Usage: pistol OPTIONS <file> [<extras> ...]

OPTIONS

-V, --version               Print version date and exit
-c, --config <config file>  configuration file to use (defaults to ~/.config/pistol/pistol.conf on Linux)
-h, --help                  print help and exit

ARGUMENTS

file                        the file to preview
extras                      extra arguments passed to the command
....

=== Integrations

==== Ranger / Lf

You can use Pistol as a file previewer in
https://ranger.github.io/[Ranger] and
https://github.com/gokcehan/lf[Lf]. For Ranger, set your
`preview_script` in your `rc.conf` as follows:

....
set preview_script ~/.go/bin/pistol
....

The same goes for Lf, but in `lfrc`:

....
set previewer ~/.go/bin/pistol
....

==== fzf

If you use https://github.com/junegunn/fzf[fzf] to search for files, you
can tell it to use `pistol` as the previewer. For example, the following
command edits with your `$EDITOR` selected python file(s) using `pistol`
as a previewer:

[source,sh]
----
$EDITOR "$(find -name '*.py' | fzf --preview='pistol {}')"
----

== Configuration

Although Pistol previews files of certain MIME types by default, it
doesn't force you to use these internal previewers for these MIME types.
You can change this behaviour by writing a configuration file in
`~/.config/pistol/pistol.conf` (or `$XDG_CONFIG_HOME/pistol/pistol.conf`) On
GNU systems, with the syntax as explained below.

NOTE: On OS X, this location defaults to:
`~/Library/Preferences/pistol/pistol.conf`, as the XDG specification imposes.

=== Syntax

You can configure preview commands according to file path or mime type
regex. The 1st word may is always interpreted first as a mime type regex
such as: `text/*`.

If a line is not matched but the 1st word is exactly `fpath`, then the
2nd argument is interpreted as a file path regex, such as:
`/var/src/.*/README.md`.

On every line, whether you used `fpath` or not, the next arguments are
the command’s arguments, where `%pistol-filename%` is substituted by
`pistol` to the file at question. You’ll see more examples in the
following sections.

Both regular expressions (for file paths and for mime types) are
interpreted by the https://golang.org/pkg/regexp/#Match[built-in
library’s `regexp.match`]. Please refer to
https://golang.org/pkg/regexp/syntax[this link] for the full reference
regarding syntax.

==== Matching Mime Types

You can inspect the MIME type of any file on a GNU/Linux OS and on Mac
OS with the command `file --mime-type <file>`.

For example, say you wish to replace Pistol’s internal text previewer
with that of https://github.com/sharkdp/bat[bat]’s, you’d put the
following in your `pistol.conf`:

....
text/* bat --paging=never --color=always %pistol-filename%
....

Naturally, your configuration file overrides the internal previewers.

Here’s another example which features http://w3m.sourceforge.net/[w3m]
as an HTML previewer:

....
text/html w3m -T text/html -dump %pistol-filename%
....

And here’s an example that leverages `ls` for printing directories’
contents:

....
inode/directory ls -l --color %pistol-filename%
....

==== Matching File Path

For example, say you wish to preview all files that reside in a certain
`./bin` directory with https://github.com/sharkdp/bat[bat]’s syntax
highlighting for bash. You could use:

....
fpath /var/src/my-bash-project/bin/[^/]+$ bat --map-syntax :bash --paging=never --color=always %pistol-filename%
....

==== A Note on RegEx matching

When Pistol parses your configuration file, as soon as it finds a match
be it a file path match or a mime type match, it stops parsing it and it
invokes the command written on the rest of the line. Therefor, if you
wish to use the examples from above which use `w3m` and `bat`, you’ll
need to put `w3m`’s line *before* `bat`’s line. Since otherwise,
`text/*` will be matched first and `text/html` won’t be checked at all.

Similarly, you’d probably want to put `fpath` lines at the top.

Of course that this is a mere example, the same may apply to any regular
expressions you’d choose to match against.

For a list of the internal regular expressions tested against when
Pistol reverts to its native previewers, read the file
https://github.com/doronbehar/pistol/blob/master/internal_writers/map.go#L8-L12[`internal_writers/map.go`].

More examples can be found in
https://github.com/doronbehar/pistol/wiki/Config-examples[this WiKi
page].

==== Quoting and Shell Piping

Pistol is pretty dumb when it parses your config, it splits all line by
spaces, meaning that e.g:

[source,txt]
----
application/json jq '.' %pistol-filename%
----

This will result in an error by https://github.com/stedolan/jq[`jq`]:

....
jq: error: syntax error, unexpected INVALID_CHARACTER, expecting $end (Unix shell quoting issues?) at <top-level>, line 1:
'.'
jq: 1 compile error
....

Indicating that `jq` got a literal `'.'`. When you run in your shell
`jq '.' file.json` you don’t get an error because your shell is
stripping the quotes around `.`. However, Pistol is not smarter then
your shell because if you’d try for example:

[source,txt]
----
application/json jq '.[] | .' %pistol-filename%
----

That would be equivalent to running in the typical shell:

[source,sh]
----
jq "\'.[]" "|" ".'" file.json
----

That’s because Pistol doesn't consider your quotes as interesting
instructions, it just splits words by spaces. Hence, to overcome this
disability, you can use:

[source,txt]
----
application/json sh: jq '.' %pistol-filename%
----

Thanks to the `sh:` keyword at the beginning of the command’s
definition, the rest of the line goes straight as a single argument to
`sh -c`.

You can worry not about quoting / escaping the rest of the line - it’s
not like when you run e.g `sh -c 'command'` in your shell where you need
to make sure single quotes are escaped or not used at all inside
`command`.

More over, with `sh:` you can use shell pipes:

[source,txt]
----
fpath .*.md$ sh: bat --paging=never --color=always %pistol-filename% | head -8
----

==== Passing arbitrary extra arguments to commands

Pistol is capable of passing extra arguments to commands if the config says so.
The arguments `%pistol-extra0%`, `%pistol-extra1%` and so on, are substituted
by the extra arguments given to `pistol`, if these present in invocation and if
they are present in the config. Example usage:

With this config:

....
fpath /problematic-bz2/.*.bz2 bzip2 %pistol-filename% %pistol-extra0%
fpath /working-bz2/.*.bz2     bzip2 %pistol-filename%
....

Running:

....
pistol /problematic-bz2/example.bz2 --test
....

Will run bzip2 while testing the integrity of the compressed file. However,
running: 

....
pistol /working-bz2/example.bz2 --test
....

Will not pass the `--test` argument to `bzip`, due to `%pistol-extra0` not
present in the config for the files at `/working-bz2`. This feature is mainly
present for usage with https://github.com/gokcehan/lf[Lf] and
https://ranger.github.io/[Ranger] which can pass width height and x, y
coordinates for image previews.

Here's an example usage for image previews that works with Lf:
footnote:[`pv` script refers to https://github.com/neeshy/lfimg/blob/e9154721514a1384a89f2713092c15dc77992f37/pv[this script].]

....
image/.* pv %pistol-filename% %pistol-extra0% %pistol-extra1% %pistol-extra2% %pistol-extra3%
....

=== Environmental Variables

Pistol’s internal previewer for text files includes syntax highlighting
thanks to the Go library https://github.com/alecthomas/chroma[chroma].
You can customize Pistol’s syntax highlighting formatting and style
through environmental variables.

==== Chroma Formatters

The term _formatter_ refers to the way the given file is presented in
the terminal. These include:

* `terminal`: The default formatter that uses terminal control codes to
change colors between every key word. This formatter has 8 colors and
it’s the default.
* `terminal256`: Same as `terminal` but with 256 colors available.
* `terminal16m`: Same as `terminal` but with 24 Bits colors i.e
True-Color.

Other formatters include `json`, and `html` but I’d be surprised if
you’ll find them useful for Pistol’s purpose.

To tell Pistol to use a specific formatter, set
`PISTOL_CHROMA_FORMATTER` in your environment, e.g:

[source,sh]
----
export PISTOL_CHROMA_FORMATTER=terminal16m
----

Recent versions of https://github.com/gokcehan/lf[Lf] support
https://github.com/gokcehan/lf/pull/93[256 colors] in its preview
window. AFAIK, footnote:[I don’t use Ranger anymore, ever since I moved to Lf. If you have
evidence it does support 256 colors, let me know and I’ll change the
default.], https://ranger.github.io/[Ranger] supports 8
colors and Lf’s `color256` isn't enabled by default.

Therefor, I decided that it’ll be best to keep this variable unset in
your general environment. If you do set `color256` in your `lfrc`, you
may feel free to set `PISTOL_CHROMA_FORMATTER` in your environment.

==== Chroma Styles

The term _style_ refers to the set of colors used to print a given file.
The chroma project documents all styles
https://xyproto.github.io/splash/docs/all.html[here] and
https://xyproto.github.io/splash/docs/longer/all.html[here].

The default style used by Pistol is `pygments`. To tell Pistol to use a
specific style set `PISTOL_CHROMA_STYLE` in your environment, e.g:

[source,sh]
----
export PISTOL_CHROMA_STYLE=monokai
----

== Debugging

Can’t figure out way does Pistol acts the way he does? You can run
pistol with:

[source,sh]
----
env PISTOL_DEBUG=1 pistol test-file
----

And you should be able to see messages that may give you a clue.

== NEWS

See https://github.com/doronbehar/pistol/releases[previous releases].
