asdf
asdf copied to clipboard
What is the best practice for making man pages accessible that are installed via asdf?
I've been using asdf for a little while now, and something I have noticed is that certain man pages for certain languages / tools aren't accessible after installing such language, i.e. elixir for example.
Presently I am using asdf v0.4.0 on macOS 10.12.6 and macOS does a little trickery to get the man pages loaded. For one, if a MANPATH environment variable is set the paths specified in /private/etc/man.conf are no longer searchable.
What does this mean? I.e. if I install the homebrew version of git it's manual / man page will become accessible via man git. However if I add say $HOME/.asdf/installs/elixir/1.5.2/man to an environment variable called MANPATH then I can no longer access the man page for git using the command man git.
The trivial solution that I have come up with is to symlink the $USER specific man pages for various languages I have installed via asdf to /usr/local/share/man which then makes them accessible, and I can run man elixir and man git.
However when I installed python 3.6.3 via asdf it was intelligent enough to add the man pages from my locally installed python and link them to the proper path. I have no idea how python set this up, is it something specific to python or something specific to the asdf-python version?
@ipatch I'm not actually sure what the state of asdf-python is when it comes to man pages, but it sounds like it's doing the right thing. This is something that all asdf plugins should do. We need to figure out what should be done, and then make the necessary changes to asdf and/or the plugin creation guide.
Spent a little time messing around with man pages this evening on macOS and something I noticed is that if there is a /path/to/super/fancy/bin and /path/to/super/fancy/man then man should be able to pick up on the man page contained in the /path/to/super/fancy/man directory. A good use for this explanation is installing GNU Coreutils with homebrew, and it will install man pages in a non standard path. This is useful for the fact that asdf can setup man pages for binaries contained within the ~/.asdf/bin. Haven't tested this on other systems yet, but this setup works well on macOS without having to fiddle with system setting files.
Got to spend some more time working with man pages on macOS, and it appears macOS may actually be able to honor the $MANPATH env var if set properly. However, something I have noticed is that if man pages are placed in certain directories then macOS will dynamically update the search path for man pages, which is quite handy when you know how it works. π
That said, personally πββοΈ I'd avoid fiddling with the $MANPATH env var because I don't know how portable it would be across various environments, ie. shells, and terminals, and also systems, ie. Linux, Darwin, etc etc.
So going back to the beginning of this circle bare with me I primarily use asdf for erlang, elixir, ocaml, and ruby. Personally, I have migrated away from using asdf to manage Node.js runtimes, and Python environments with other version manager tools. So when I still need to work with erlang and elixir it's quite nice to pop open a man page to figure some stuff out, which is something I haven't been able to do when installing erlang or elixir via asdf.
So from hacking away at man pages this morning, I came to the conclusion that when I install elixir on macOS there are indeed man pages located in the install dir path;
πpath to where asdf installs precompiled language files for elixir on my macOS box.
/opt/Code/github/public/version-managers/asdf/installs/elixir/[MAJOR.MINOR.PATCH]
add whatever major minor and patch version of elixir is installed on the system, for my use case it would be 1.7.4. The "stock" contents of a elixir 1.7.4 install on my box looks like,
β°βΞ» pwd
/opt/Code/github/public/version-managers/asdf/installs/elixir/1.7.4
β°βΞ» l
total 44K
drwxr-xr-x 11 capin staff 352 Nov 4 12:18 ./
drwxr-xr-x 3 capin staff 96 Nov 4 12:18 ../
drwxr-xr-x 3 capin staff 96 Nov 4 12:18 .mix/
drwxr-xr-x 11 capin staff 352 Oct 25 03:40 bin/
drwxr-xr-x 8 capin staff 256 Nov 4 12:18 lib/
drwxr-xr-x 9 capin staff 288 Oct 25 03:41 man/
-rw-r--r-- 1 capin staff 15K Oct 25 03:40 CHANGELOG.md
-rw-r--r-- 1 capin staff 12K Oct 25 03:40 LICENSE
-rw-r--r-- 1 capin staff 825 Oct 25 03:40 NOTICE
-rw-r--r-- 1 capin staff 7.3K Oct 25 03:40 README.md
-rw-r--r-- 1 capin staff 5 Oct 25 03:40 VERSION
and the first thing pops out to me is the man directory. There are in fact a couple of man pages within that directory that are useful for working with mix, iex, and elixir commands from a shell. However, with a stock install of asdf on macOS, /usr/bin/man is unable to locate those man pages in the directory listed above. However, extending from my previous comments, if I run
man -d iex
I'll see something like, towards the end / bottom of STDOUT
OUTPUT
No manual entry for iex
However v2, if I remove /path/to/asdf/shims and /path/to/asdf/bin from $PATH and add /opt/Code/github/public/version-managers/asdf/installs/elixir/1.7.4/bin to my $PATH, and verify the above path to the elixir bins are indeed in my $PATH
echo $PATH
The above should output the newly added bin directory where asdf installed the elixir bins, not the symlinked shell scripts that reside in the shims directory. Now I can execute,
man -d iex
and notice how macOS uses manpath mappings to locate certain man pages for binaries located in various paths throughout the system. So noticing that, and doing a little troubleshooting, ie. trial & error I came to the conclusion if I add a share/man/man1 directory in within the elixir install path, ie.
/opt/Code/github/public/version-managers/asdf/installs/elixir/[MAJOR.MINOR.PATCH]/share/man/man1
and then run
man -d iex
I get something like the below;
OUTPUT
not executing command:
(cd '/opt/Code/github/public/version-managers/asdf/installs/elixir/1.7.4/share/man' && (echo ".ll 11.2i"; echo ".nr LL 11.2i"; /bin/cat '/opt/Code/github/public/version-managers/asdf/installs/elixir/1.7.4/share/man/man1/iex.1') | /usr/bin/tbl | /usr/bin/groff -Wall -mtty-char -Tascii -mandoc -c | (/usr/bin/less -is || true))
now that macOS has mapped /path/to/asdf/install/elixir/version/share/man/man1
I can read the man page for iex from within any directory on my system without having to explicitly set the path to the man page, and I did not need to fiddle with any super user; ie. root owned files on the system for man page configuration. So all in all I made some progress with understanding how man pages work on macOS. However v3, this isn't a permanent solution but rather an explanation of what is going on with man pages on macOS, and hopefully begins a dialogue of how we should get asdf to read man pages from languages, runtimes, various other utilities asdf manages.
cheers π» Chris
Wow! Thanks for taking the time to document all that. That sounds promising. I guess we need to figure out if this is something OSX specific or if this is behavior baked into man. If it's something in man itself that does this is there any reason we shouldn't add /share/man/man1 to each version?
Wow! Thanks for taking the time to document all that. That sounds promising. I guess we need to figure out if this is something OSX specific or if this is behavior baked into
man. If it's something inmanitself that does this is there any reason we shouldn't add/share/man/man1to each version?
Yeah man, when I get some more free time, I explore things on a Debian 9.x box I have access to, and see if the same things apply, just not quite sure when this will happen, will post updates in this issue.
cheers Chris
FWIW, MANPATH generally gets respected in Ubuntu and Fedora
can we make it "mandatory" that plugins install the manual pages if they are available? ~/.local/share/man should be respected in all Unix environment, as far as i know. i love asdf and i use to manage the versions of all of my development tools, but most of the plugins not installing the man pages is a almost deal breaker for me. i know this might be biting more than i can chew, but i could go over a few plugins and create PRs for the installation of man pages to the correct location, which i assume is TBD as per this issue.
@bigodel
can we make it "mandatory" that plugins install the manual pages if they are available?
I am not sure how we would enforce that.
I believe the root cause is that few people know how man pages work and we do not have any documentation covering how a plugin author should handle this. After reading this thread I still do not know how I would support all *nix OSs in my plugins.
If you have a clear understanding of how this should work, I would happily accept PRs to our plugin author docs specific to man pages.
For what it is worth I have created a man_asdf utility here on GitHub. By default showing the man page of the current version, but if a version is given as a 2nd param the man page for that version is displayed. I use it frequently for checking various versions of Python and Tmux, to quickly see what version various things are compatible with.
This is an old ticket, and I'm sure everyone has been forced to cook themselves up some sort of solution.
I tried the script @jaclu provided and it fell apart for "vim" which has about eight shims for the same plugin and the manpages are links to each other. The same for nodejs, where asdf_man npm was impossible.
I implemented a simple solution WITHIN asdf ( ~/.asdf/lib/commands/command-man.bash )
# -*- sh -*-
man_command() {
local shim_name
shim_name="$1"
shift
local -a args=( "$@" )
if [ -z "$shim_name" ]; then
printf "usage: asdf man <command>\\n"
exit 1
fi
handle_man_command() {
local plugin_dir=
local plugin_name="$1"
local version="$2"
local -a mandirs=()
plugin_dir="$(asdf_data_dir)/installs/${plugin_name}/${version}"
readarray -t mandirs < <(find "${plugin_dir}" -type d -name man -print)
[ "${#mandirs[*]}" -gt 0 ] || exit 1
MANPATH="$(IFS=':'; echo "${mandirs[*]}")" man "${args[@]:-$shim_name}"
}
with_shim_executable "$shim_name" handle_man_command || exit 1
}
man_command "$@"
Now asdf man vim will either return non-zero result, or display the man pages provided with the plugin.
I have a bash function
man() {
asdf man "$@" || command man "$@"
}
which allows me to type man vim, and have the manuals for the vim plugin appear.
It also supports
asdf man <command> <other manpage>, for those cases where man-pages are installed for configuration files for example, which are not themselves present as plugins or shims.
Any thoughts? I dont love the use of "find" to locate the man pages, but it was very quickly apparent that there was no consistency between plugins as to where the man pages landed. There is also no way to specify a man page for any version other than the currently active version.
I would love for the the plugin architecture to be improved to add support for standardizing how man pages were handled, but I suspect that won't happen.
Any thoughts? I dont love the use of "find" to locate the man pages, but it was very quickly apparent that there was no consistency between plugins as to where the man pages landed. There is also no way to specify a man page for any version other than the currently active version.
i think that using find is our best option indeed.
i like it very much, i am adopting it to my own workflow with a slight modification:
--- command-man.bash.orig 2021-12-14 14:30:04.960348022 -0300
+++ command-man.bash 2021-12-14 14:14:49.252926478 -0300
@@ -1,32 +1,46 @@
# -*- sh -*-
man_command() {
- local shim_name
- shim_name="$1"
+ local shim_name
+ shim_name="$1"
+
+ if [ -z "$shim_name" ]; then
+ printf "usage: asdf man <command>\\n"
+ exit 1
+ fi
+
+ local section
+ if [[ $shim_name =~ \([[:digit:]]+.*\) ]]; then
+ section="$(echo $shim_name | sed -e 's/.*(\(.*\))/\1/')"
+ shim_name="$(echo $shim_name | cut -d'(' -f1)"
+ elif [[ $shim_name =~ \..* ]]; then
+ section="$(echo $shim_name | cut -d'.' -f2)"
+ shim_name="$(echo $shim_name | cut -d'.' -f1)"
+ elif [[ $shim_name =~ [[:digit:]] && $# -eq 2 ]]; then
+ section="$1"
+ shim_name="$2"
shift
+ fi
- local -a args=( "$@" )
+ shift
- if [ -z "$shim_name" ]; then
- printf "usage: asdf man <command>\n"
- exit 1
- fi
+ local -a args=( "$@" )
- handle_man_command() {
- local plugin_dir=
- local plugin_name="$1"
- local version="$2"
- local -a mandirs=()
+ handle_man_command() {
+ local plugin_dir=
+ local plugin_name="$1"
+ local version="$2"
+ local -a mandirs=()
- plugin_dir="$(asdf_data_dir)/installs/${plugin_name}/${version}"
- readarray -t mandirs < <(find "${plugin_dir}" -type d -name man -print)
+ plugin_dir="$(asdf_data_dir)/installs/${plugin_name}/${version}"
+ readarray -t mandirs < <(find "${plugin_dir}" -type d -name man -print)
- [ "${#mandirs[*]}" -gt 0 ] || exit 1
+ [ "${#mandirs[*]}" -gt 0 ] || exit 1
- MANPATH="$(IFS=':'; echo "${mandirs[*]}")" man "${args[@]:-$shim_name}"
- }
+ MANPATH="$(IFS=':'; echo "${mandirs[*]}")" man "$section" "${args[@]:-$shim_name}"
+ }
- with_shim_executable "$shim_name" handle_man_command || exit 1
+ with_shim_executable "$shim_name" handle_man_command || exit 1
}
man_command "$@"
this allows for calls such as asdf man <section> <command> for the section <section> of the manual of <command>, but also asdf man <command>.<section> and asdf man <command>(<section>); e.g.
asdf man nodeasdf man node.1asdf man 'node(1)'asdf man 1 node
are all equivalent. note, however, that the last syntax is only valid when you are visiting the man page for the command itself, and for other man pages you'd have to specify it differently, such as asdf man node 5 npmrc or (more readable) asdf man node npmrc.5.
it is also worth noting that, due to how asdf works, the options need to be passed after the command itself, so if you'd like to run, e.g., man -t node, you'd need to do it as asdf man node -t, which should be reflected in the usage. i didn't add much to it, but i'd be happy to elaborate more and create a more concise help message if the changes are working and make sense.