|
""" |
|
A shared option boilerplate with click. |
|
|
|
The top-level do NOT collect enough information about shared options because of how click work. |
|
To achieve the goal, the logic handling shared options must be postponed until shared options in interest are collected. |
|
|
|
===================== |
|
This is free and unencumbered software released into the public domain. |
|
|
|
Anyone is free to copy, modify, publish, use, compile, sell, or |
|
distribute this software, either in source code form or as a compiled |
|
binary, for any purpose, commercial or non-commercial, and by any |
|
means. |
|
|
|
In jurisdictions that recognize copyright laws, the author or authors |
|
of this software dedicate any and all copyright interest in the |
|
software to the public domain. We make this dedication for the benefit |
|
of the public at large and to the detriment of our heirs and |
|
successors. We intend this dedication to be an overt act of |
|
relinquishment in perpetuity of all present and future rights to this |
|
software under copyright law. |
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. |
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR |
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, |
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR |
|
OTHER DEALINGS IN THE SOFTWARE. |
|
|
|
For more information, please refer to <http://unlicense.org/> |
|
""" |
|
|
|
from dataclasses import dataclass |
|
from typing import Any, Dict |
|
|
|
import click |
|
|
|
|
|
@dataclass() |
|
class Repo: |
|
""" |
|
Being a dataclass for convenience |
|
The logic handling shared options is located in `finish()` |
|
""" |
|
verbose: int = 0 |
|
finished: bool = False |
|
|
|
def filter_options(self, kwargs) -> Dict[str, Any]: |
|
"""Return a dict without shared options""" |
|
if self.finished: |
|
raise ValueError("finish() has been called") |
|
|
|
kwargs = dict(kwargs) |
|
self.verbose += kwargs.pop('verbose', 0) |
|
return kwargs |
|
|
|
def consume_options(self, kwargs): |
|
"""All options are consumed or raise""" |
|
empty = self.filter_options(kwargs) |
|
|
|
if empty: |
|
raise ValueError(f"Unknown option {next(iter(empty))}") |
|
|
|
def finish(self, options=None): |
|
if options: |
|
self.consume_options(options) |
|
|
|
if self.finished: |
|
raise ValueError("finish() has been called") |
|
self.finished = True |
|
|
|
del options |
|
|
|
verbose = self.verbose |
|
print(f"Configure logging based on verbosity = {verbose}") |
|
|
|
|
|
pass_repo = click.make_pass_decorator(Repo, ensure=True) |
|
|
|
|
|
def shared_options(func=None): |
|
def decorate(fn): |
|
options = ( |
|
click.option('--verbose', '-v', count=True), |
|
) |
|
|
|
for opt in options: |
|
fn = opt(fn) |
|
|
|
fn = pass_repo(fn) |
|
|
|
return fn |
|
|
|
if callable(func): |
|
return decorate(func) |
|
return decorate |
|
|
|
|
|
@click.group() |
|
@shared_options() |
|
def cli(repo: Repo, **kwargs): |
|
repo.consume_options(kwargs) |
|
|
|
print('repo in cli', repo) |
|
|
|
|
|
@cli.group() |
|
@shared_options() |
|
def group_a(repo: Repo, **kwargs): |
|
repo.consume_options(kwargs) |
|
|
|
print('repo in feed', repo) |
|
|
|
|
|
@group_a.command() |
|
@click.argument('argument') |
|
@shared_options() |
|
def command_b(repo: Repo, argument: str, **kwargs): |
|
repo.finish(kwargs) |
|
|
|
print("Argument is", argument) |
|
|
|
|
|
if __name__ == '__main__': |
|
cli() |