tomerfiliba / plumbum

Plumbum: Shell Combinators
https://plumbum.readthedocs.io
MIT License
2.8k stars 182 forks source link

Display help message in multi-line format? #555

Open tashrifbillah opened 3 years ago

tashrifbillah commented 3 years ago

Way to reproduce

python multi_line.py -h

multi_line.py ```python #!/usr/bin/env python from plumbum import cli class TopupEddyEpi(cli.Application): '''Epi and eddy correction using topup and eddy_openmp/cuda commands in fsl For more info, see: https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/eddy/UsersGuide https://fsl.fmrib.ox.ac.uk/fsl/fslwiki/topup/TopupUsersGuide You can also view the help message: `eddy_openmp` or `eddy_cuda` `topup` ''' dwi_file= cli.SwitchAttr( ['--imain'], help='''--imain primary4D,secondary4D/3D primary: one 4D volume input, should be PA; secondary: another 3D/4D volume input, should be AP, which is opposite of primary 4D volume''', mandatory=True) def main(): pass if __name__== '__main__': TopupEddyEpi.run() ```

Expected

As I wrote in the program:

--imain VALUE:str    primary4D,secondary4D/3D
                     primary: one 4D volume input, should be PA;
                     secondary: another 3D/4D volume input, should be AP, which is opposite of primary 4D volume

Actual

Spanning across my terminal in an untidy way:

    --imain VALUE:str      --imain primary4D,secondary4D/3D primary: one 4D volume input, should be PA; secondary: another 3D/4D
                           volume input, should be AP, which is opposite of primary 4D volume; required

Is the expected at all possible?

ink-splatters commented 2 years ago

According to the code which handles it, it seems it does not assume multiline help messages:

https://github.com/tomerfiliba/plumbum/blob/3876425a04f934667e1f91ba88f526ca6976dbd8/plumbum/cli/application.py#L930-L953

henryiii commented 2 years ago

Probably could be fixed, PR welcome.

ink-splatters commented 2 years ago

@henryiii @tashrifbillah here's what I've come up with. I could do the PR but ETA is unspecified (otherwise let someone go ahead)


first, let's split the text by line breaks:

>>> text = "This is first paragraph, of average length.\n" \
...        "And this is the second one.\n" \
...        "That's the longest paragraph, you can see it yourself."
>>> stext = text.splitlines()
>>> stext
['This is first paragraph, of average length.', 'And this is the second one.', "That's the longest paragraph, you can see it yourself."]

now, wrap the previous stage' result using TextWrapper (with width=15 for demo purpose):

>>> stext=text.splitlines()
>>> list(map(TextWrapper(width=15).wrap, stext))
[['This is first', 'paragraph, of', 'average length.'], ['And this is the', 'second one.'], ["That's the", 'longest', 'paragraph, you', 'can see it', 'yourself.']]

finally, lets create wrap() function which will also flatten the list and join it using specified indent:

>>> from textwrap import TextWrapper
>>> from typing import List
>>> 
>>> 
>>> def wrap(text: str, max_width: int, indent_size: int = 4):
...     indent=' ' * indent_size
...     return '\n'.join([
...         line for lst in map(
...             TextWrapper(width=max_width,  
...                         initial_indent=indent,
...                         subsequent_indent=indent).wrap,
...             text.splitlines()
...         )
...         for line in lst
...     ])
... 
>>> 
>>> text = """
... This is first paragraph, of average length.\n
... And this is the second one.\n
... That's the longest paragraph, you can see it yourself.
... """
>>> 
>>> print(wrap(text, max_width=15), '\n')
    This is
    first
    paragraph,
    of average
    length.
    And this is
    the second
    one.
    That's the
    longest
    paragraph,
    you can see
    it
    yourself. 

>>> print(wrap(text, max_width=80, indent_size=8))
        This is first paragraph, of average length.
        And this is the second one.
        That's the longest paragraph, you can see it yourself.
tashrifbillah commented 2 years ago

Hi, I shall try to do it myself. I have marked the email notification.

tashrifbillah commented 2 years ago

Hi @ink-splatters , sorry about the delay--the idea that you came up with, should it be inserted before line 931?

tashrifbillah commented 2 years ago

I inserted this block:

            # break and join multi-line help message
            max_width= 200
            indent_size= 8
            indent=' ' * indent_size
            help= '\n'.join([
                line for lst in map(
                    TextWrapper(width=max_width,
                                initial_indent=indent,
                                subsequent_indent=indent).wrap,
                    help.splitlines()
                )
                for line in lst
            ])

after line 931 but no effect. I believe this block joins them back together :(

https://github.com/tomerfiliba/plumbum/blob/0c574a99ef0a2419c2490db86bc8180e249c91a6/plumbum/cli/application.py#L945-L947

tashrifbillah commented 2 years ago

Replacing the latter block with msg= help gets me the following:

Switches:
    --imain VALUE:str              --imain primary4D,secondary4D/3D
        primary: one 4D volume input, should be PA;
        secondary: another 3D/4D volume input, should be AP, which is opposite of primary 4D volume; required

I see that the second and third lines of my help message do not follow the indent of first line but it is some progress :) Please let me know how we can improve upon it.

ink-splatters commented 2 years ago

Hi @ink-splatters , sorry about the delay--the idea that you came up with, should it be inserted before line 931?

Hi @tashrifbillah, I was completely incapable to answer on time, sorry for that, as well as contributing more, by personal reasons. It will likely change, probably soon.

Thanks a lot