Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Request] Some additional stylings #4

Closed
pcroland opened this issue Sep 6, 2022 · 11 comments
Closed

[Request] Some additional stylings #4

pcroland opened this issue Sep 6, 2022 · 11 comments
Labels
enhancement New feature or request

Comments

@pcroland
Copy link

pcroland commented Sep 6, 2022

Hi!

First of all, thank you for this library. It's really promising. This is what I currently use:

prog_name = 'substoforced'
prog_version = '1.0.1'


class RParse(argparse.ArgumentParser):
    def _print_message(self, message, file=None):
        if message:
            if message.startswith('usage'):
                message = f'[bold cyan]{prog_name}[/bold cyan] {prog_version}\n\n{message}'
                message = re.sub(r'(-[a-z]+\s*|\[)([A-Z]+)(?=]|,|\s\s|\s\.)', r'\1[{}]\2[/{}]'.format('bold color(231)', 'bold color(231)'), message)
                message = re.sub(r'((-|--)[a-z]+)', r'[{}]\1[/{}]'.format('green', 'green'), message)
                message = message.replace('usage', f'[yellow]USAGE[/yellow]')
                message = message.replace('options', f'[yellow]FLAGS[/yellow]', 1)
                message = message.replace(self.prog, f'[bold cyan]{self.prog}[/bold cyan]')
            message = f'[not bold white]{message.strip()}[/not bold white]'
            print(message)


class CustomHelpFormatter(argparse.RawTextHelpFormatter):
    def _format_action_invocation(self, action):
        if not action.option_strings or action.nargs == 0:
            return super()._format_action_invocation(action)
        default = self._get_default_metavar_for_optional(action)
        args_string = self._format_args(action, default)
        return ', '.join(action.option_strings) + ' ' + args_string


parser = RParse(
    add_help=False,
    formatter_class=lambda prog: CustomHelpFormatter(prog)
)
parser.add_argument('-h', '--help',
                    action='help',
                    default=argparse.SUPPRESS,
                    help='show this help message.')
parser.add_argument('-v', '--version',
                    action='version',
                    version=f'[bold cyan]{prog_name}[/bold cyan] [not bold white]{prog_version}[/not bold white]',
                    help='show version.')
parser.add_argument('-s', '--sub',
                    default=argparse.SUPPRESS,
                    help='specifies srt input')
parser.add_argument('-f', '--folder',
                    metavar='DIR',
                    default=None,
                    help='specifies a folder where [bold color(231)]SubsMask2Img[/bold color(231)] generated timecodes (optional)\nyou should remove the junk from there manually')
args = parser.parse_args()

With this I get a result like this:
img
I don't think all of these is currently possible with rich-argparse, but it would be great. These are the missing pieces compared to this (at least I think):

  • no styling for the comma between long and short option (I think this should be the default but an option would be nice)
  • missing colon after groups
  • separate styling for metavar
  • uppercase "usage" to match groups
  • same styling for usage options as in groups
  • rename default group (options)
  • option to print version before help or other additional texts (I guess this is possible but I don't know how)
@hamdanal
Copy link
Owner

hamdanal commented Sep 7, 2022

Thanks for the feedback. I'll try to respond to all your comments:

  • missing colon after groups

This is not something that the formatter should do by default as the goal is formatting without altering text as much as possible. That said, you have the option to set RichHelpFormatter.group_name_formatter to do whatever you want (I just realized I forgot to mention it in the readme, sorry about that, I will write about it soon) as you can see the example below.

  • no styling for the comma between long and short option (I think this should be the default but an option would be nice)
  • separate styling for metavar

This is a fair request and I would look into it, but it will complicate the implementation so I don't think this will happen any time soon.

  • uppercase "usage" to match groups
  • same styling for usage options as in groups

The uppercase "usage" can be done now by overriding the RichHelpFormatter.add_usage method (see example below).
As for styling the usage text in general, I currently use the Syntax feature from rich for its simplicity. I intend to change that in the future for a custom regex highlighter where the options in the usage will have the same format as in the groups and the "usage" prefix will follow other groups formatting.

  • rename default group (options)

This is not related to the formatter. The formatter just uses whatever group names you pass to it. The solution IMO is to create your own group with the name you want (see example below). Another solution is to ask CPython to change the default in argparse (If I remember correctly, someone already did it when they change the default group name from optional arguments to options in a recent version of python)

  • option to print version before help or other additional texts (I guess this is possible but I don't know how)

This is considered altering the structure of the help text, so I can't have the formatter do that. The formatter is only meant to style the help text in a better way than the default formatters provided by argparse without altering its structure. However, what you want is also available by overriding the RichHelpFormatter.add_usage method (or argparse.HelpFormatter.add_usage in general) to add the text you want before the usage as shown in the example below.

import argparse

from rich_argparse import RichHelpFormatter

prog_name = 'substoforced'
prog_version = '1.0.1'
version_text = f'[bold cyan]{prog_name}[/bold cyan] [not bold white]{prog_version}[/not bold white]'


class CustomHelpFormatter(RichHelpFormatter):
    def add_usage(self, usage, actions, groups, prefix=None) -> None:
        self.add_text(version_text)  # <-- print version before help
        if prefix is None:
            prefix = 'USAGE: '  # <-- "usage" prefix with same formatting as the groups
        return super().add_usage(usage, actions, groups, prefix=prefix)


CustomHelpFormatter.group_name_formatter = lambda name: name.upper() + ':'  # <-- custom group name formatting
CustomHelpFormatter.styles['argparse.text'] = 'default'  # <-- if you don't want the default (italic)
parser = argparse.ArgumentParser(
    prog_name,
    add_help=False,
    formatter_class=CustomHelpFormatter
)
flags_group = parser.add_argument_group('flags')  # <-- custom group replacing "options"
flags_group.add_argument('-h', '--help',
                    action='help',
                    default=argparse.SUPPRESS,
                    help='show this help message.')
flags_group.add_argument('-v', '--version',
                    action='version',
                    version=version_text,
                    help='show version.')
flags_group.add_argument('-s', '--sub',
                    default=argparse.SUPPRESS,
                    help='specifies srt input')
flags_group.add_argument('-f', '--folder',
                    metavar='DIR',
                    default=None,
                    help='specifies a folder where [bold color(231)]SubsMask2Img[/bold color(231)] generated timecodes (optional)\nyou should remove the junk from there manually')
args = parser.parse_args()

@hamdanal hamdanal added the question Further information is requested label Sep 7, 2022
hamdanal added a commit that referenced this issue Sep 7, 2022
thanks to @ pcroland for the feedback in #4
@pcroland
Copy link
Author

pcroland commented Sep 7, 2022

I'm looking forward for the updates. Wouldn't it be possible that the library was a replacement for argparse instead of a replacement for it's helpformatter? It would give the users more control over what can be customized and probably easier syntaxes. For example you could do something like this:

parser = rparse.ArgumentParser(
    prog_name,
    add_help=False,
    syntax_color=xy,
    metavar_color=etc
    ...
)

I also have another feature idea. I know it's a little over the top but it would be visually pleasing. The idea is to allow the long options to align with other long options when you have short options with different length. For example you could align them by allowing extra spaces. If you were to set the allowed extra spaces to one, then this:

  -drc, --dynamic-range-compression
  -k, --keeptemp              keep temp files
  -mo, --measure-only         kills DEE when the dialnorm gets written to the progress bar
  -c, --config                show config and config location(s)
  -gc, --generate-config      generate a new config

would become this:

  -drc, --dynamic-range-compression
  -k,  --keeptemp             keep temp files
  -mo, --measure-only         kills DEE when the dialnorm gets written to the progress bar
  -c,  --config               show config and config location(s)
  -gc, --generate-config      generate a new config

@hamdanal
Copy link
Owner

hamdanal commented Sep 9, 2022

Wouldn't it be possible that the library was a replacement for argparse instead of a replacement for it's helpformatter?

No, I wouldn't add yet another library for CLI argument parsing.
Also, it would be impossible then to use it with any third party code that defines its custom parser class (say django commands).
Additionally, I would trust a third party help formatter more than I would trust a third party argument parser. Look at it as in the worst case scenario where the formatter has bugs, code depending on the parser should still work fine in production, it is only the --help that might be affected.

I also have another feature idea. I know it's a little over the top but it would be visually pleasing. ...

Sorry but I don't think I will add this. I would like to keep the implementation as simple as possible and this would complicate things. Also, using multiple chars in short options is against the posix conventions as it messes with stacking short options together (i.e. somthing like -ab in place of -a -b) so it is not recommended.

P.S. I might have some free time this weekend to look into your request regarding metavar formatting

@hamdanal
Copy link
Owner

I got the metavar coloring adjustable with RichHelpFormatter.styles['argparse.metavar'] in https://github.com/hamdanal/rich-argparse/tree/metavar-style. Please give it a try.
I'll be working on the "usage" coloring next.

@michelcrypt4d4mus
Copy link
Contributor

i've been doing some kind of crazy stuff with Rich since i discovered it recently1 and was pretty stoked to find this new project. Wanted to offer some feedback based on my obsessive tweaking of Rich configurations to try to maximize the usefulness of data being presented to the end user.

These are just opinions and they're mostly about the default config. I know i can adjust it but a) first impressions are important and b) most potential users will not adjust the defaults. Springboarding them with some good design would probably go a long way towards improving the usefulness of this project as well as its adoption.

  1. i think it would be good design to have the default metavar styling be noticeably different from but still related to the color of the option itself.
  2. i think it's a little odd to color the options in one color at the top line (red/pink) and another one in the detailed view. coloring things that are the same with the same color helps human beings understand immediately that they are the same thing.
  3. the orange for group headings is great; really makes the sections jump out. but IMHO either the group label or the group description should be italic, not both. I'd vote for making the group name not italic. In fact I'd vote for making only the longform description italic along with the option names in the detailed view
  4. i would vote for having the long and short names of the option be very similar but noticeably different colors. just as an example they could be bright_cyan for the short name and cyan for the long name
  5. optional args should less brightly colored than required args

i'll try to come up with a screenshot of tweaks to the defaults along these lines; easier to discuss visual stuff w/visuals

Footnotes

  1. at least crazy enough that the author of Rich tweeted out some images i created along with the word "intense" and an exclamation point

@hamdanal
Copy link
Owner

Thanks for the feedback @michelcrypt4d4mus. To answer your questions:

  1. This is already in progress and will change in the next release.
  2. As I said in [Request] Some additional stylings #4 (comment), I am working on changing that. This is a tough change as it requires ditching a simple call to rich.syntax.Syntax for a complex implementation that a simple regex cannot cut. Hopefully I will get an implementation that cover at least the most common uses soon.
  3. In an effort to make the default styling less "sparkly", in the upcoming release I am removing the italic style from all the defaults styles. Users who prefer to have it can of course still set it in their code.
  4. I prefer to not to add more knobs for now, it already became very complicated to get everything working properly and every time I change something it leads to more and more bugs
  5. Same answer as the previous. Also positional args can be optional and --options can be required so coloring depending on whether the arg is required or not doesn't seem like a good idea

@michelcrypt4d4mus
Copy link
Contributor

before i respond to any individual points i will throw out there that my python andRich skills/interest are probably at the top of their game right now and i'd be more than happy to poke around if you could put just a very short section in the README about the best way to get a dev env up and running. i've made a fair amount of private python packages in my day but it's been a while and also i suspect there may some gotchas given the nature of the project.

beyond that just to offer some more feedback/ideas that you should feel free to ignore:

  1. re: italics i actually think a few subtle italics are really great! I would leave them on the optional switches (left col main table). w/the caveat that i have not really thought this through, maybe a design rule could be that optional arguments (the actual arg, not the metavar) are lowercase italicized lowercase and anything required is lowercase not italicized?
  2. re: the top vs. side coloring keep it fun. btw i can't seem to upload screenshots but i also have a weird miscoloring - in [--max-decode-length MAX] only in the top panel... the length is a brighter shade of pink.
  3. re: fixing one thing breaks another - i was going to say "maybe pytest can help and then i looked and saw the very impressive ratio of pytest code to project code already going on in her, so let me be the first to congratulate you for doing that.

@hamdanal
Copy link
Owner

Thanks, probably the easiest way to get a dev env is using tox --devenv venv. I'll add a "contributing" section to the readme in the future.

  1. maybe in the future, for now they will be off by default.
  2. this is a bug caused by the current use of Syntax with awk lexer . I'll open a ticket to point that out. It will be solved with the new usage parser I am working on.
  3. yeah unfortunately even with 100% test coverage stuff beak

@hamdanal hamdanal added enhancement New feature or request and removed question Further information is requested labels Sep 26, 2022
@michelcrypt4d4mus
Copy link
Contributor

michelcrypt4d4mus commented Sep 28, 2022

been messing around with some color themes/tweaks; feel free to take a look (i think this one is my favorite)

occurred to me that offering a couple of default options as far as theming could be cool

@hamdanal
Copy link
Owner

@pcroland I have implemented a bunch of stuff that may interest you, It is still not published on PyPI yet but you can test it with pip install git+https://github.com/hamdanal/rich-argparse.git@main.

These are all now implemented (and the default)

  • no styling for the comma between long and short option (I think this should be the default but an option would be nice)
  • missing colon after groups
  • separate styling for metavar
  • uppercase "usage" to match groups
  • same styling for usage options as in groups

With this version, your example roughly becomes

import argparse

from rich_argparse import RichHelpFormatter

version = "1.0.1"
version_text = f"[bold cyan]%(prog)s[/] {version}"


class CustomHelpFormatter(RichHelpFormatter):
    def add_usage(self, usage, actions, groups, prefix=None) -> None:
        self.add_text(version_text)
        return super().add_usage(usage, actions, groups, prefix=prefix)


CustomHelpFormatter.styles["argparse.args"] = "green"
CustomHelpFormatter.styles["argparse.groups"] = "yellow"
CustomHelpFormatter.styles["argparse.metavar"] = "bold color(231)"
parser = argparse.ArgumentParser(
    "substoforced", add_help=False, formatter_class=CustomHelpFormatter
)
flags_group = parser.add_argument_group("flags")
flags_group.add_argument("-h", "--help", action="help", help="show this help message.")
flags_group.add_argument(
    "-v", "--version", action="version", version=version_text, help="show version."
)
flags_group.add_argument("-s", "--sub", default=argparse.SUPPRESS, help="specifies srt input")
flags_group.add_argument(
    "-f",
    "--folder",
    metavar="DIR",
    default=None,
    help=(
        "specifies a folder where [bold color(231)]SubsMask2Img[/] generated timecodes (optional)"
        "\nyou should remove the junk from there manually"
    ),
)
args = parser.parse_args()

I'll close the issue but feel free to reopen it if needed; Thanks for the feedback

@pcroland
Copy link
Author

Nice, thanks for the update, it works great. I'm going to update my stuff with it once it's on PyPI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants