Fpm-man|fman describes Fortran intrinsics with a single-file version

con

Fortran intrinsics descriptions are available in several forms, as
described at

but one way is as an fpm plug-in called “fpm-man”. The plug-in has had
its interactive mode expanded. A single-file version for testing is at

fpm-man.f90

If in the bash shell, make interactive mode the default by entering

export LINES

and to turn it back off again enter

declare +x LINES

(Currently) in other environments you have to set the environment variable
LINES to the number of lines your terminal window is displaying, or add
the option “–lines NNNN” where NNNN is the number of lines in your terminal
window.

All the descriptions can be loaded at once. Assuming your terminal emulator
supports ANSI color escape sequences

fpm man manual --lines 24 --color
n
n
n
h
q

The help text for fpm was originally written to be used with fpm-man. It
is still possible to take advantage of this, even though it is currently
partially manual:

fpm manual >fpm.txt
fpm man --lines 24
L fpm.txt
c
f
f
b

The positioning commands repeat, so “y” moves down one line, and after entering it just hit the carriage return to repeat until another command is entered. And then “^” goes up one line. The commands can be displayed by entering “h”…

Is there any CLI interface to displaying the stdlib routines other than perhaps lynx/links/…?
Ideally it would work off-line? Is anything planned as a standard format for describing repository packages that will be easily searchable?

the commands are similiar to less(1) and more(1) but for portability to not use raw text mode, so they have a “repeat” feature where a carriage return repeats the last movement or search

+------------+-----+----------------------+------+------ so---------------------+
 |POSITIONING:| b   | back one page        | f    | forward one page(default) |
 |            | u   | up 1/2 page          | d    | down 1/2 page             |
 |            | e   | up 1 line, eeeeee... | y    | down 1 line, yyyyyy...    |
 |            | gNNN| goto Nth line        | NNN  | go to Nth line            |
 +------------+-----+----------------------+------+---------------------------+
 |SEARCH:     | /RE | search for expression| ?RE  | backward search           |
 |            | n   | repeat last search   | N    | repeat last search upward |
 +------------+-----+----------------------+------+---------------------------+
 |SYSTEM:     | s F | save to filename     | xcmd | execute system_command    |
 +------------+-----+----------------------+------+---------------------------+
 |OPTIONS:    | #   | toggle line numbers  | lNNN | change lines per page     |
 |            | i   | toggle search by case| c    | toggle color mode         |
 +------------+-----+----------------------+------+---------------------------+
 |GENERAL:    | q   | quit                 | r    | refresh                   |
 |            | h   | display help         | T    | reload Table Of Contents  |
 |            | tstr| load specified topic |      |                           |
 +------------+-----+----------------------+------+---------------------------+
 | An empty string repeats the last positioning or toggle command. So if you  |
 | searched for a string or did an "e" or "y" and then just hit return the    |
 | previous command is repeated until a non-blank command like "r" is entered.|
 +----------------------------------------------------------------------------+
3 Likes

If my understanding is correct, being a fpm plugin means that you can type fpm man (a subcommand of fpm) instead of fpm-man (the name of the executable).

But can you please explain the plugin mechanisms under the hood? How fpm is detecting the plugins installed in the system?

Another question about:

It seems the fpm subcommand manual is hidden: it does not appear when I type fpm or fpm --help. Is there any reason?

I asked grok to download the gfortran documentation for a few intrinsic functions in JSON format and to write a Python program to display information about a requested intrinsic function. For a JSON file such as

{
	"sum": {
		"description": "Adds the elements of an array along a specified dimension if the corresponding element in an optional mask is .TRUE. If no dimension is specified, it sums all elements.",
		"syntax": "RESULT = SUM(ARRAY[, DIM][, MASK])",
		"arguments": {
			"ARRAY": "An array of type INTEGER, REAL, or COMPLEX",
			"DIM": "Optional integer specifying the dimension along which to sum",
			"MASK": "Optional logical array of the same shape as ARRAY, determining which elements to include"
		},
		"return_value": "Same type as ARRAY. Scalar if DIM is absent; otherwise, an array of rank one less than ARRAY.",
		"standard": "Fortran 95 and later",
		"example": "INTEGER :: x(5) = (/ 1, 2, 3, 4, 5 /)\nPRINT *, SUM(x)                  ! Outputs 15\nPRINT *, SUM(x, MASK=MOD(x, 2)==1) ! Sums odd elements: 9"
	},
	"product": {
		"description": "Multiplies the elements of an array along a specified dimension if the corresponding element in an optional mask is .TRUE. If no dimension is specified, it multiplies all elements.",
		"syntax": "RESULT = PRODUCT(ARRAY[, DIM][, MASK])",
		"arguments": {
			"ARRAY": "An array of type INTEGER, REAL, or COMPLEX",
			"DIM": "Optional integer specifying the dimension along which to multiply",
			"MASK": "Optional logical array of the same shape as ARRAY"
		},
		"return_value": "Same type as ARRAY. Scalar if DIM is absent; otherwise, an array of rank one less than ARRAY.",
		"standard": "Fortran 95 and later",
		"example": "INTEGER :: x(3) = (/ 2, 3, 4 /)\nPRINT *, PRODUCT(x)              ! Outputs 24\nPRINT *, PRODUCT(x, MASK=x>2)    ! Outputs 12"
	},
	"minval": {
		"description": "Determines the minimum value of the elements in an array, or along a specified dimension if provided. Only considers elements where an optional mask is .TRUE.",
		"syntax": "RESULT = MINVAL(ARRAY[, DIM][, MASK])",
		"arguments": {
			"ARRAY": "An array of type INTEGER or REAL",
			"DIM": "Optional integer specifying the dimension",
			"MASK": "Optional logical array of the same shape as ARRAY"
		},
		"return_value": "Same type as ARRAY. Scalar if DIM is absent; otherwise, an array of rank one less than ARRAY. Returns HUGE(ARRAY) if empty or all mask elements are .FALSE.",
		"standard": "Fortran 95 and later",
		"example": "INTEGER :: x(4) = (/ 5, 2, 9, 1 /)\nPRINT *, MINVAL(x)               ! Outputs 1\nPRINT *, MINVAL(x, MASK=x>3)     ! Outputs 5"
	}
}
type or paste code here

and Python code man.py

import json
import sys

# Load the JSON data
with open('fortran.json', 'r') as f:
    intrinsics = json.load(f)

def print_intrinsic_info(name):
    name = name.lower()
    if name in intrinsics:
        info = intrinsics[name]
        print(f"Intrinsic: {name.upper()}")
        print(f"Description: {info['description']}")
        print(f"Syntax: {info['syntax']}")
        print("Arguments:")
        for arg, desc in info['arguments'].items():
            print(f"  {arg}: {desc}")
        print(f"Return Value: {info['return_value']}")
        print(f"Standard: {info['standard']}")
        print("Example:")
        print(info['example'])
    else:
        print(f"Intrinsic '{name}' not found.")

# Check command-line argument
if len(sys.argv) < 2:
    print("Usage: python script.py <intrinsic_name>")
else:
    query = sys.argv[1]
    print_intrinsic_info(query)

python man.py minval gives

Intrinsic: MINVAL
Description: Determines the minimum value of the elements in an array, or along a specified dimension if provided. Only considers elements where an optional mask is .TRUE.
Syntax: RESULT = MINVAL(ARRAY[, DIM][, MASK])
Arguments:
ARRAY: An array of type INTEGER or REAL
DIM: Optional integer specifying the dimension
MASK: Optional logical array of the same shape as ARRAY
Return Value: Same type as ARRAY. Scalar if DIM is absent; otherwise, an array of rank one less than ARRAY. Returns HUGE(ARRAY) if empty or all mask elements are .FALSE.
Standard: Fortran 95 and later
Example:
INTEGER :: x(4) = (/ 5, 2, 9, 1 /)
PRINT *, MINVAL(x) ! Outputs 1
PRINT *, MINVAL(x, MASK=x>3) ! Outputs 5

A Fortran man utility should give information about an intrinsic procedure just by supplying its name.

1 Like

As is generally true of any plugin feature of any program the idea is to allow features to be accessed like they were part of the main application that can be developed and maintained separately. This helps prevent feature bloat and allows for plugins to be written in different languages and for different OSes. The initial prototype plugin mechanism is simple. If a subcommand is given that is not recognized look through the user search-path for a command called fpm-<NAME> and call that program. fpm-man and fpm-search were the first two plugins designed to be integrated this way. The discussions about it in the fpm github site give more insight but the next stage was long in coming. Just calling the plugin without an API or an interface into fpm that was well defined prevents the plugin capability from being fully exploited. Put you can now use the ‘fpm -dump’ option to dump the state to JSON and then use the Fortran JSON library or that of any other language to allow for much more powerful plugins. There were other options but it is a big step forward that the state is in a standard format now; and that you can call that dump interface grammatically as well using fpm as a dependency package.

Put in its simplest form it just starts with looking for unknown subcommands as external commands called fpm-<NAME> and passing the remaining arguments to that command if found, using a procedure called “which” for portability (it would be done by calling system commands, but the command varies between shells and systems and is relatively easy to create with standard fortran, so there is a “which” subroutine in the fpm source). I use plugins a lot to call the gprof profiler and run the codes in the test directory (I would like a “benchmark/” directory like /example and /test for that but since fpm takes wildcards it is pretty easy to group tests) for timing purposes just by entering ‘fpm time’; or use the GNU debugger in screen mode just by entering ‘fpm gdb’ or dump the metadata in a pretty format from the manifest file fpm.toml, dump out a text version of the description of the fpm.toml file (particularly useful when on a machine that is not on the www) and so on. Now with the new JSON/dump interface I hope to make more powerful ones that are generic enough to be generally useful.

To dump the entire manual you can enter “fpm --help manual” which is documented, and “help” is equivalent to “–help” which is stated in the document so “fpm help manual” will do the same so to keep the basic help as terse and concise as possible “fpm manual”, which does the same thing, is not displayed along with the main commands. Other things like “fpm help runner” and that “…” is equivalent to “–all” and “*” for filenames and a few other nuggets are not documented or are harder to find because tastes vary or prototypes of ideas are still being evaluated or repetition/redundancy is being avoided or because someone forgot. In the case of “fpm manual”
I like it but since “fpm help manual” does the same thing and is documented it could be removed.

Not sure the text has been held to conform to txt2man rules, but the original idea was you could create a man-page and HTML document to place on the fpm webpage by entering

     fpm manual|txt2man >fpm.1fpm

and do the same for each command "ie. fpm help build |txt2man>fpm-build.1fpm) and
then use groff and a CSS style sheet to make HTML pages for the fpm documentation pages that would be run in the CD/CI scripts automatically so that each release would automatically have build-in help, man-pages, and www pages generated. This went for the Fortran intrinsic descriptions in fpm-man as well.

Some parallel evolution with other packages is of note. ‘git help status’ and ‘git --help status’ produce the same text as ‘man git status’ .

The best argument for removing it is because it would interfere with creating a plugin called ‘fpm-manual’ and it is not documented and is redundant with ‘fpm help manual’ and that fpm is still a prototype so backward compatibility is not as crucial.

Do you want it documented, removed, or were you just curious?

wget https://raw.githubusercontent.com/lockstockandbarrel/mars/refs/heads/main/bootstrap/fman.f90
gfortran fman.f90 -o fman
fman minval

just give fman/fpm-man a topic and it displays the help, so I think I am missing something there.

Grokking and asking it to make markdown versions for the fortran-lang site of the intrinsics would
have saved a lot of time. fman tries to be compiler-agnostic so it intentionally avoids gfortran-specific documentation, and is intentionally more long-winded about the dusty corners and was designed to generate man-pages and www pages in HTML and markdown and a CLI utility automatically from the same description with the intent the community would take over and improve the prototype descriptions, which took a good deal of typing; but I suspect chatGPT or grok could probably do the same in a few minutes. If I had just procrastinated more about starting that I wonder if I could just do all of that in a few AI requests. Sigh.

For all that, for browser use I think I still like the slidy version best

Fortran intrinsics using slidy

2 Likes

Thanks for your precise answer about fpm plugins, and fpm manual.

Concerning fpm manual, I was both curious and think it should be documented because I learned many things about fpm by reading its output (~15 pages). I think that every fpm user should print and read it. I have for example discovered that fpm new --full my_project is generating a fully documented fpm.toml manifest (~6 pages). It is another document that every user should read entirely.

A few things to think about then …

I do find that fpm manual > fpm.txt and then editing the file is the most common way I usually use the help text myself, so maybe it should be listed up-front in the subcommand list, as in retrospect it takes quite a bit of reading to find the documented form (fpm help manual).

Perhaps fpm manual should include a manifest (ie. fpm.toml) section by default.

The fpm new --all option does not appear to have been updated so although not incorrect it is incomplete and could stand a .refresh.

PS:

fpm man manual

puts all the intrinsic descriptions into a single flat text file as well. Even though fpm-man has had search capabilities added as described above, sometimes scanning with an editor is the most powerful approach.

1 Like

@urbanjost
I have tried to build your project in Ubuntu 24.10, gfortran 14.2, fpm 0.11:

$ git clone git@github.com:urbanjost/M_intrinsics.git
$ cd M_intrinsics/
$ fpm build

The build stops here:

[ 81%] Compiling...
app/fpm-man.f90:92:49:
...
app/fpm-man.f90:782:1:

  782 | elemental impure function run(command)
      | 1
Error: Unclassifiable statement at (1)
app/fpm-man.f90:807:3:

  807 | end function run
      |   1
Error: Expecting END SUBROUTINE statement at (1)
compilation terminated due to -fmax-errors=1.
<ERROR> Compilation failed for object " app_fpm-man.f90.o "
<ERROR> stopping due to failed compilation
STOP 1

The code starting from line 781 is:

subroutine setup()
elemental impure function run(command)
! ident_1="@(#) fman run(3f) funcion to call execute_command_line()"
use, intrinsic :: iso_fortran_env, only : stderr=>ERROR_UNIT, stdout=>OUTPUT_UNIT
implicit none
character(len=*),intent(in) :: command
...

The code of the run() function seems to have been pasted at the start of the subroutine setup().
Or am I doing something wrong?

I have also noticed that the following link in the README.md is dead:

A single-file version of the CLI program fpm-man(1) is in fman.f90.

It should be fman.F90 I think.

After moving the subroutine setup() line in the right place, I have successfully compiled and run:

$ gfortran fman.F90

Thanks. When I get to a place where I can access that I will duplicate your fixs. I build and run it before pushing it so I think I bumped the wrong key at the wrong moment in vim while double-checking the file for tab characters and extraneous whitespace at the last moment. Otherwise, that is very puzzling.

1 Like

Yes, it just looks like an accidental copy/paste.

is just an example of auto-formatting the output of fpm manual. Will take it down shortly, as just an example. But it shows that might be worth automating, I think.

I ran the old auto-conversion commands to convert the help text to man-page and html and markdown and it looks like just a few tweeks to the fpm built-in help descriptions and changing the titles to be camel-case and smaller and it would not look bad at all. It could then be set up to send a PR to the fpm documentation site and/or create a doc on the fpm github site automatically as originally intended; as so far I see extensive descriptions on the manifest file, a tutorial and some examples but no description of the fpm command usage as provided by the help on the fpm site (? might have missed it ?). Anyway, just a few small tweeks that should be easy to get passed if leave out the CD/CI auto-generation seems due.

1 Like

Thanks, this morning it now runs fine:

$ git clone git@github.com:urbanjost/M_intrinsics.git
$ cd M_intrinsics/
$ fpm build --profile release
$ fpm install
$ fpm man sinh --color | more
*set_mode* unknown key name auto_response_file

sinh(3fortran)                                                 sinh(3fortran)

NAME
  SINH(3) - [MATHEMATICS:TRIGONOMETRIC] Hyperbolic sine function
...

The fpm install command has installed fpm-man in my .local/bin/ directory. And it is now recognized by fpm as a plugin :grinning_face:

What is your advice to make --color option a default? Put an alias in my .bashrc?