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

Add table style for option formatting #25

Merged
merged 10 commits into from
Feb 17, 2021

Conversation

yudai-nkt
Copy link
Contributor

Rewrite of #12 in a backward-compatible manner.

One difference from what @florimondmanca and I discussed is that I named the option :option-style:. This is more descriptive than style and easy to extend when we want to define styles for other sections such as usage.

Fix #11.

@yudai-nkt
Copy link
Contributor Author

I'm afraid I cannot figure out how to fix mypy failure...

@max-frank
Copy link
Contributor

max-frank commented Dec 13, 2020

First of all thank you for your on this PR! I as a user of this project would also highly appreciate table style options. After seeing your PR I looked into the mypy issue in effort to be helpful, but sadly as of now I could also not find a solution.

In the process howerever I took a closer look at clicks usage parsing code. As a result I have a few notes in regards to implementation, but feel free to ignore this.

It seems click uses get_help_record(ctx) (linked code is the code for 7.1.2). for its on parsing. This function does most of the custom parsing for types you are implementing for this PR already. From what I can see it returns Tupel('[-<short option>,] --<long option> <type>', '<help>') which might be why you decided to parse the information yourself.

For example for your test cases it would return:

  • ('-d, --debug TEXT', 'Include debug output')
  • ('--choice [foo|bar]', 'The given help text')
  • ('--date [%Y-%m-%d]', 'The given help text')

For float range 7.* sadly does not have support for displaying the range yet, but the current alpha version of 8.0 supports this already:

  • click==7.*: ('--range-a FLOAT RANGE', 'The given help text')
  • click==8.*: ('--range-a FLOAT RANGE', 'The given help text [0<=x<=1]')

Note how in contrast to choice and date the range is part of the help text and not the command + type part of the returned tuple. Also there is no indication of if clam is true or false.

Also something to note about the upcoming 8.0 release is that it will introduce ctx.to_info_dict() for click.Context giving developers direct access to everything so custom documentation will be easier etc. (pallets/click#461) Below you will find an example dict generated using the context of your test case.

Now my suggestion for the table feature is two fold:

  1. For now implement a basic table style using get_help_record(ctx). To get formatted option types. This will be compatible with 7.* and also with the future release of 8.. If you wish to support meaningful type doc for things not supported by 7. (such as FloatRange etc.) you could do custom formatting only for these types while leaving click to handle all other types (e.g., choice, datetime).
  2. Once 8.* is released a third style (only available for >=8.0) could be introduced
    that is able to create a more complex table using on the context dict.

**edit: note about custom support for FloatRanges

Context Dict:

Click to expand!
{
    "command": {
        "name": "hello-table",
        "params": [
            {
                "name": "debug",
                "param_type_name": "option",
                "opts": [
                    "-d",
                    "--debug"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "String",
                    "name": "text"
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": None,
                "envvar": None,
                "help": "Include debug output",
                "prompt": None,
                "is_flag": false,
                "flag_value": true,
                "count": false,
                "hidden": false
            },
            {
                "name": "choice",
                "param_type_name": "option",
                "opts": [
                    "--choice"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "Choice",
                    "name": "choice",
                    "choices": [
                        "foo",
                        "bar"
                    ],
                    "case_sensitive": true
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": "foo",
                "envvar": None,
                "help": None,
                "prompt": None,
                "is_flag": false,
                "flag_value": false,
                "count": false,
                "hidden": false
            },
            {
                "name": "date",
                "param_type_name": "option",
                "opts": [
                    "--date"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "DateTime",
                    "name": "datetime",
                    "formats": [
                        "%Y-%m-%d"
                    ]
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": None,
                "envvar": None,
                "help": None,
                "prompt": None,
                "is_flag": false,
                "flag_value": true,
                "count": false,
                "hidden": false
            },
            {
                "name": "range_a",
                "param_type_name": "option",
                "opts": [
                    "--range-a"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "FloatRange",
                    "name": "float range",
                    "min": 0,
                    "max": 1,
                    "min_open": false,
                    "max_open": false,
                    "clamp": false
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": 0,
                "envvar": None,
                "help": "The given help text",
                "prompt": None,
                "is_flag": false,
                "flag_value": true,
                "count": false,
                "hidden": false
            },
            {
                "name": "range_b",
                "param_type_name": "option",
                "opts": [
                    "--range-b"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "FloatRange",
                    "name": "float range",
                    "min": 0,
                    "max": None,
                    "min_open": false,
                    "max_open": false,
                    "clamp": false
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": None,
                "envvar": None,
                "help": None,
                "prompt": None,
                "is_flag": false,
                "flag_value": true,
                "count": false,
                "hidden": false
            },
            {
                "name": "range_c",
                "param_type_name": "option",
                "opts": [
                    "--range-c"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "FloatRange",
                    "name": "float range",
                    "min": None,
                    "max": 1,
                    "min_open": false,
                    "max_open": false,
                    "clamp": false
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": 0,
                "envvar": None,
                "help": None,
                "prompt": None,
                "is_flag": false,
                "flag_value": true,
                "count": false,
                "hidden": false
            },
            {
                "name": "help",
                "param_type_name": "option",
                "opts": [
                    "--help"
                ],
                "secondary_opts": [],
                "type": {
                    "param_type": "Bool",
                    "name": "boolean"
                },
                "required": false,
                "nargs": 1,
                "multiple": false,
                "default": false,
                "envvar": None,
                "help": "Show this message and exit.",
                "prompt": None,
                "is_flag": true,
                "flag_value": true,
                "count": false,
                "hidden": false
            }
        ],
        "help": "Hello, world!",
        "epilog": None,
        "short_help": None,
        "hidden": false,
        "deprecated": false
    },
    "info_name": None,
    "allow_extra_args": false,
    "allow_interspersed_args": true,
    "ignore_unknown_options": false,
    "auto_envvar_prefix": None
}

@max-frank
Copy link
Contributor

@yudai-nkt OK I have now found the reason and a fix for the mypy errors

The problem seems to stem from the fact that the typeshed for click types (third party type hints) is missing the type hints for the attributes.

e.g., FloatRange is currently defined as

class FloatRange(FloatParamType):
    def __init__(self, min: Optional[float] = ..., max: Optional[float] = ..., clamp: bool = ...) -> None: ...

but should be

class FloatRange(FloatParamType):
    min: Optional[float]
    max: Optional[float]
    clamp: bool

    def __init__(self, min: Optional[float] = ..., max: Optional[float] = ..., clamp: bool = ...) -> None: ...

Now this means to ultimately fix the problem one would have to make a PR for typeshed and add the missing attribute type hints in third_party/2and3/click/types.pyi (ideally for all click types and not only the 4 that are used in this PR). Now since this is not an immediate fix (with mypy having to release with the typeshed changes for it to work) the only other option I see is to ignore the mypy errors on this for now.

@yudai-nkt
Copy link
Contributor Author

I don't have much time now (it's 2:00 am here and I was about go to bed haha), but I'd like to answer to some of your comment which I can react to right now.

It seems click uses get_help_record(ctx) (linked code is the code for 7.1.2). for its on parsing. This function does most of the custom parsing for types you are implementing for this PR already. From what I can see it returns Tupel('[-<short option>,] --<long option> <type>', '<help>') which might be why you decided to parse the information yourself.

For example for your test cases it would return:

  • ('-d, --debug TEXT', 'Include debug output')
  • ('--choice [foo|bar]', 'The given help text')
  • ('--date [%Y-%m-%d]', 'The given help text')

I actually once used get_help_record() in yudai-nkt/mkdocs-click@6f89573, but it wasn't handy for me because it mixes option names and type annotation into a single element. Another disadvantage is the lack of whitespaces around | delimiters in choice and datetime. This prevents the Type column from line-breaking and might result in a bad table appearance.

For float range 7.* sadly does not have support for displaying the range yet, but the current alpha version of 8.0 supports this already:

  • click==7.*: ('--range-a FLOAT RANGE', 'The given help text')
  • click==8.*: ('--range-a FLOAT RANGE', 'The given help text [0<=x<=1]')

Range support in v8 seems good, but I like being verbal (i.e., between, above, and below) rather than using mathematical expression given that HTML documentation has more room than terminal emulator. It's just a matter of preference of course.

Thank you for your suggestions and information anyway, @max-frank! I'll read it thoroughly later and consider which direction I should go (pallets/click#461 especially looks quite promising).

@max-frank
Copy link
Contributor

I actually once used get_help_record() in yudai-nkt/mkdocs-click@6f89573, but it wasn't handy for me because it mixes option names and type annotation into a single element. Another disadvantage is the lack of whitespaces around | delimiters in choice and datetime. This prevents the Type column from line-breaking and might result in a bad table appearance.

I was suspecting this was the case. I should have also looked at your old PR sorry.

Range support in v8 seems good, but I like being verbal (i.e., between, above, and below) rather than using mathematical expression given that HTML documentation has more room than terminal emulator. It's just a matter of preference of course.

Indeed its just a matter of preference one thing to note though starting with v8 FloatRange and IntRange will support configuring if the range is inclusive or exclusive for both ends. Just something to keep in mind so that the code will be easily adaptable for future 8.* support.

お疲れ様

Copy link
Contributor

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! This is looking good. :)

I left several comments on the PR.

For comments marked as "(Nit)", I'm happy to ignore those for now and get back to it later with a follow-up cleanup PR. Other comments are a bit more blocking to merging this though.

mkdocs_click/_docs.py Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
README.md Outdated Show resolved Hide resolved
mkdocs_click/_extension.py Outdated Show resolved Hide resolved
mkdocs_click/_docs.py Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
tests/unit/test_docs.py Outdated Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
@florimondmanca
Copy link
Contributor

@max-frank Great one on submitting a PR to typeshed to fix the typing errors.

@yudai-nkt For now we can just # type: ignore attribute accesses such as param_type.min, and perhaps add a link to python/typeshed#4813 to serve as a TODO.

@yudai-nkt
Copy link
Contributor Author

@florimondmanca I updated the branch according to your comments.

@max-frank After some thought, I'd like to keep the initial direction but will try the v8 features you introduced me after the stable version is released. Then I'll submit a PR if it turns out to be worth it.

Copy link
Contributor

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking great! Some last-round questions / suggestions.

tests/unit/test_docs.py Outdated Show resolved Hide resolved
mkdocs_click/_docs.py Outdated Show resolved Hide resolved
@florimondmanca florimondmanca self-requested a review January 7, 2021 14:54
Copy link
Contributor

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems ready to me. I have some cleanup ideas but I'll treat that as a follow-up.

Thanks so much for your work on this @yudai-nkt and @max-frank for reviewing. :-)

@florimondmanca florimondmanca changed the title Define two styles for option formatting Add table style for option formatting Feb 17, 2021
@florimondmanca florimondmanca merged commit ecc2548 into mkdocs:master Feb 17, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Print options in a sleek table format
3 participants