|  | """ | 
        
          |  | 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() |