wizlib 0.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wizlib might be problematic. Click here for more details.

wizlib/__init__.py ADDED
File without changes
wizlib/app.py ADDED
@@ -0,0 +1,85 @@
1
+ import sys
2
+ from dataclasses import dataclass
3
+ import os
4
+ from pathlib import Path
5
+
6
+ from wizlib.class_family import ClassFamily
7
+ from wizlib.command import WizHelpCommand
8
+ from wizlib.super_wrapper import SuperWrapper
9
+ from wizlib.parser import WizParser
10
+
11
+
12
+ RED = '\033[91m'
13
+ RESET = '\033[0m'
14
+
15
+
16
+ class WizApp:
17
+ """Root of all WizLib-based CLI applications. Subclass it. Can be
18
+ instantiated and then run multiple commands."""
19
+
20
+ base_command = None
21
+ name = ''
22
+
23
+ @classmethod
24
+ def main(cls): # pragma: nocover
25
+ """Call this from a __main__ entrypoint"""
26
+ cls.run(*sys.argv[1:], debug=os.getenv('DEBUG'))
27
+
28
+ @classmethod
29
+ def run(cls, *args, debug=False):
30
+ """Call this from a Python entrypoint"""
31
+ try:
32
+ cls.initialize()
33
+ app = cls(*args)
34
+ # if app.ready:
35
+ command = app.first_command
36
+ result = command.execute()
37
+ if result:
38
+ print(result, file=sys.stdout, end='')
39
+ if sys.stdout.isatty(): # pragma: nocover
40
+ print()
41
+ if command.status:
42
+ print(command.status, file=sys.stderr)
43
+ except Exception as error:
44
+ if debug:
45
+ raise error
46
+ else:
47
+ print(f"\n{RED}{type(error).__name__}: " +
48
+ f"{error}{RESET}\n", file=sys.stderr)
49
+ sys.exit(1)
50
+
51
+ @classmethod
52
+ def initialize(cls):
53
+ """Set up the app class to parse arguments"""
54
+ cls.parser = WizParser(
55
+ prog=cls.name,
56
+ exit_on_error=False)
57
+ for handler in cls.base_command.handlers:
58
+ cls.parser.add_argument(
59
+ f"--{handler.name}",
60
+ f"-{handler.name[0]}",
61
+ type=handler.named(cls.name),
62
+ default='')
63
+ subparsers = cls.parser.add_subparsers(dest='command')
64
+ for command in cls.base_command.family_members('name'):
65
+ key = command.get_member_attr('key')
66
+ aliases = [key] if key else []
67
+ subparser = subparsers.add_parser(command.name, aliases=aliases)
68
+ command.add_args(subparser)
69
+
70
+ def __init__(self, *args):
71
+ args = args if args else [self.base_command.default]
72
+ self.vals = vars(self.parser.parse_args(args))
73
+ self.first_command = self.get_command(**self.vals)
74
+
75
+ def get_command(self, **vals):
76
+ """Run a single command"""
77
+ if 'help' in vals:
78
+ return WizHelpCommand(**vals)
79
+ else:
80
+ command_name = vals.pop('command')
81
+ command_class = self.base_command.family_member(
82
+ 'name', command_name)
83
+ if not command_class:
84
+ raise Exception(f"Unknown command {command_name}")
85
+ return command_class(**vals)
wizlib/class_family.py ADDED
@@ -0,0 +1,120 @@
1
+ from importlib import import_module
2
+ from inspect import getmodule
3
+ from pathlib import Path
4
+ from re import match
5
+
6
+
7
+ class ClassFamily:
8
+
9
+ # Return an attribute of this specific class, not inherited.
10
+
11
+ @classmethod
12
+ def get_member_attr(self, attribute):
13
+ if attribute in self.__dict__:
14
+ return self.__dict__[attribute]
15
+
16
+ # True or False: This class has all the attributes provided. Return True if
17
+ # no attributes provided. Use __subclasses__ so we only look at direct
18
+ # descendents, and __dict__ so we only look at attributes defined here (not
19
+ # inherited).
20
+
21
+ @classmethod
22
+ def has_member_attrs(self, *attributes):
23
+
24
+ # Do I have all the attributes?
25
+ matches = [a in self.__dict__ for a in attributes]
26
+
27
+ # Python's all() function returns True if the list is empty. So we kill
28
+ # two birds with one stone here - get a hit if there are no attributes,
29
+ # and avoid the empty-matches problem.
30
+ return all(matches + list(attributes))
31
+
32
+ # Return a set of myself and all my descendents that have certain
33
+ # attributes. If no attribute names are provided, return the entire family.
34
+
35
+ @classmethod
36
+ def family_members(self, *attributes):
37
+
38
+ # Put myself into a set if I qualify
39
+ hits = {self} if self.has_member_attrs(*attributes) else set()
40
+
41
+ # Go through my subclasses
42
+ for kid in self.family_children():
43
+
44
+ # Call the same function on each subclass
45
+ hits |= kid.family_members(*attributes)
46
+
47
+ # Send it back
48
+ return hits
49
+
50
+ @classmethod
51
+ def family_attrs(self, attribute):
52
+ """
53
+ Return a set of all the values of a specific attribute that exist in
54
+ the family. The set avoids repetition of values in the result.
55
+ """
56
+ # Put myself into a set if I qualify
57
+ if self.has_member_attrs(attribute):
58
+ values = {self.get_member_attr(attribute)}
59
+ else:
60
+ values = set()
61
+
62
+ # Go through my subclasses
63
+ for kid in self.family_children():
64
+
65
+ # Call the same function on each subclass
66
+ values |= kid.family_attrs(attribute)
67
+
68
+ # Send it back
69
+ return values
70
+
71
+ @classmethod
72
+ def family_member(self, attribute, value):
73
+ """Return a specific family member that has a specified value of an
74
+ attribute. Assume that there is only one, so return it as soon as it's
75
+ found."""
76
+
77
+ # See if I qualify, and if so, return myself. Use __dict__ rather than
78
+ # hasattr() and getattr() to refer only to myself, rather than
79
+ # inherited attributes.
80
+ if attribute in self.__dict__:
81
+ if self.__dict__[attribute] == value:
82
+ return self
83
+
84
+ # Go through the subclasses, doing the same. The return statement will
85
+ # exit the loop, so we get out as soon as we find one, without having
86
+ # to evaluate others.
87
+ for kid in self.family_children():
88
+ found = kid.family_member(attribute, value)
89
+ if found:
90
+ return found
91
+
92
+ # Import the whole family, so they show up as subclasses. Then return this
93
+ # classes subclasses. This obviates the need to import everything at
94
+ # initialization. It happens when needed, and only once. Family members
95
+ # must be in modules (files) in the same directory as the parent (me).
96
+ # Furthermore, all the files in that directory will be imported. The only
97
+ # exception is a file with the same name as myself, to avoid circular
98
+ # imports. This is a private method, intended to be called when needed.
99
+
100
+ @classmethod
101
+ def family_children(self):
102
+
103
+ # We only need to import the family once
104
+ if not hasattr(self, '_family_imported'):
105
+
106
+ # Find the location of the directory and the name of the module
107
+ module = getmodule(self)
108
+ directory = Path(module.__file__).parent
109
+ module_name = module.__package__
110
+
111
+ # Go through the directory and import everything into the module
112
+ for import_file in directory.iterdir():
113
+ if match(r'^[^_].*\.py$', import_file.name):
114
+ import_module(f'{module_name}.{import_file.stem}')
115
+
116
+ # Prevent it from needing to run again
117
+ self._family_imported = True
118
+
119
+ # Return the subclasses (children)
120
+ return self.__subclasses__()
wizlib/command.py ADDED
@@ -0,0 +1,45 @@
1
+ from argparse import ArgumentParser
2
+ from pathlib import Path
3
+
4
+ from wizlib.class_family import ClassFamily
5
+ from wizlib.config_handler import ConfigHandler
6
+ from wizlib.input_handler import InputHandler
7
+ from wizlib.super_wrapper import SuperWrapper
8
+
9
+
10
+ class WizCommand(ClassFamily, SuperWrapper):
11
+ """Define all the args you want, but stdin always works."""
12
+
13
+ status = ''
14
+ handlers = []
15
+
16
+ @classmethod
17
+ def add_args(self, parser):
18
+ """Add arguments to the command's parser - override this.
19
+ Add global arguments in the base class. Not wrapped."""
20
+ pass
21
+
22
+ def __init__(self, **vals):
23
+ for key in vals:
24
+ setattr(self, key, vals[key])
25
+ for handler in self.handlers:
26
+ if handler.name not in vals:
27
+ setattr(self, handler.name, handler())
28
+
29
+ def handle_vals(self):
30
+ """Clean up vals, calculate any, ask through UI, etc. - override
31
+ this and call super().handle_vals()."""
32
+ pass
33
+
34
+ def execute(self, method, *args, **kwargs):
35
+ """Actually perform the command - override and wrap this via
36
+ SuperWrapper"""
37
+ self.handle_vals()
38
+ result = method(self, *args, **kwargs)
39
+ return result
40
+
41
+
42
+ class WizHelpCommand(WizCommand):
43
+
44
+ def execute(self):
45
+ return self.help
@@ -0,0 +1,87 @@
1
+ from argparse import Namespace
2
+ from pathlib import Path
3
+ import os
4
+ from dataclasses import dataclass
5
+ from unittest.mock import patch
6
+
7
+ from yaml import load
8
+ from yaml import Loader
9
+ from wizlib.handler import Handler
10
+
11
+ from wizlib.error import ConfigHandlerError
12
+
13
+
14
+ class ConfigHandler(Handler):
15
+ """
16
+ Handle app-level configuration, where settings could come from specific
17
+ settings (such as from argparse), environment variables, or a YAML file.
18
+ Within the Python code, config keys are underscore-separated all-lower.
19
+
20
+ A ConfigHandler returns null in the case of a missing value, assuming that
21
+ commands can handle their own null cases.
22
+ """
23
+
24
+ name = 'config'
25
+
26
+ def __init__(self, value=None):
27
+ self.file = value
28
+ self.cache = {}
29
+
30
+ @property
31
+ def yaml(self):
32
+ if hasattr(self, '_yaml'):
33
+ return self._yaml
34
+ path = None
35
+ if self.file:
36
+ path = Path(self.file)
37
+ elif self.appname:
38
+ localpath = Path.cwd() / f".{self.appname}.yml"
39
+ homepath = Path.home() / f".{self.appname}.yml"
40
+ if (envvar := self.env(self.appname + '-config')):
41
+ path = Path(envvar)
42
+ elif (localpath.is_file()):
43
+ path = localpath
44
+ elif (homepath.is_file()):
45
+ path = homepath
46
+ if path:
47
+ with open(path) as file:
48
+ self._yaml = load(file, Loader=Loader)
49
+ return self._yaml
50
+
51
+ @staticmethod
52
+ def env(name):
53
+ if (envvar := name.upper().replace('-', '_')) in os.environ:
54
+ return os.environ[envvar]
55
+
56
+ def get(self, key: str):
57
+ """Return the value for the requested config entry"""
58
+
59
+ # If we already found the value, return it
60
+ if key in self.cache:
61
+ return self.cache[key]
62
+
63
+ # Environment variables take precedence
64
+ if (result := self.env(key)):
65
+ self.cache[key] = result
66
+ return result
67
+
68
+ # Otherwise look at the YAML
69
+ if (yaml := self.yaml):
70
+ split = key.split('-')
71
+ while (val := split.pop(0)) and (val in yaml):
72
+ yaml = yaml[val] if val in yaml else None
73
+ if not split:
74
+ self.cache[key] = yaml
75
+ return yaml
76
+
77
+ @classmethod
78
+ def fake(cls, **vals):
79
+ """Return a fake ConfigHandler with forced values, for testing"""
80
+ handler = cls()
81
+
82
+ def fake_env(name):
83
+ key = name.replace('-', '_')
84
+ if key in vals:
85
+ return vals[key]
86
+ handler.env = fake_env
87
+ return handler
wizlib/error.py ADDED
@@ -0,0 +1,2 @@
1
+ class ConfigHandlerError(Exception):
2
+ pass
wizlib/handler.py ADDED
@@ -0,0 +1,14 @@
1
+ from argparse import Action
2
+
3
+
4
+ class Handler:
5
+ """Base class for handlers"""
6
+
7
+ appname = None
8
+
9
+ @classmethod
10
+ def named(cls, name):
11
+ """Subclass of the handler that holds the app name as a closure"""
12
+ class NamedHandler(cls):
13
+ appname = name
14
+ return NamedHandler
@@ -0,0 +1,26 @@
1
+ from pathlib import Path
2
+ import sys
3
+
4
+ from wizlib.handler import Handler
5
+
6
+
7
+ class InputHandler(Handler):
8
+
9
+ name = 'input'
10
+ text: str = ''
11
+
12
+ def __init__(self, file=None, stdin=True):
13
+ if file:
14
+ self.text = Path(file).read_text()
15
+ elif stdin and (not sys.stdin.isatty()):
16
+ self.text = sys.stdin.read()
17
+
18
+ def __str__(self):
19
+ return self.text
20
+
21
+ @classmethod
22
+ def fake(cls, value):
23
+ """Return a fake InputHandler with forced values, for testing"""
24
+ handler = cls(stdin=False)
25
+ handler.text = value
26
+ return handler
wizlib/parser.py ADDED
@@ -0,0 +1,54 @@
1
+ """A drop-in replacement for ArgumentParser that always raises exceptions
2
+ for argument errors (including unrecognized arguments) and returns help
3
+ messages in a 'help' item in the resulting namespace. Useful especially for
4
+ REPLs."""
5
+
6
+
7
+ from argparse import ArgumentParser
8
+ from argparse import ArgumentError
9
+ from argparse import Action
10
+ from argparse import SUPPRESS
11
+ from contextlib import redirect_stdout
12
+ from io import StringIO
13
+ import sys
14
+ import os
15
+
16
+
17
+ class WizHelpAction(Action):
18
+
19
+ def __init__(self,
20
+ option_strings,
21
+ dest=SUPPRESS,
22
+ default=SUPPRESS,
23
+ help=None):
24
+ super(WizHelpAction, self).__init__(
25
+ option_strings=option_strings,
26
+ dest=dest,
27
+ default=default,
28
+ nargs=0,
29
+ help=help)
30
+
31
+ def __call__(self, parser, namespace, values, option_string=None):
32
+ with redirect_stdout(output := StringIO()):
33
+ parser.print_help()
34
+ output.seek(0)
35
+ setattr(namespace, self.dest, output.read())
36
+
37
+
38
+ class WizArgumentError(ArgumentError):
39
+
40
+ def __init__(self, message):
41
+ self.argument_name = None
42
+ self.message = message
43
+
44
+
45
+ class WizParser(ArgumentParser):
46
+
47
+ def __init__(self, **vals):
48
+ vals['exit_on_error'] = False
49
+ vals['add_help'] = False
50
+ super().__init__(**vals)
51
+ self.add_argument('--help', '-h', action=WizHelpAction)
52
+
53
+ def error(self, *args, **vals):
54
+ raise WizArgumentError(str(args[0]))
wizlib/rlinput.py ADDED
@@ -0,0 +1,71 @@
1
+ try: # pragma: nocover
2
+ import gnureadline as readline
3
+ except ImportError: # pragma: nocover
4
+ import readline
5
+ import sys
6
+
7
+
8
+ # Use tab for completion
9
+ readline.parse_and_bind('tab: complete')
10
+
11
+ # Only a space separates words
12
+ readline.set_completer_delims(' ')
13
+
14
+
15
+ # Readline defaults to complete with local filenames. Definitely not what we
16
+ # want.
17
+
18
+ def null_complete(text, start): # pragma: nocover
19
+ return None
20
+
21
+
22
+ readline.set_completer(null_complete)
23
+
24
+
25
+ def rlinput(prompt: str = "", default: str = "",
26
+ options: list = None): # pragma: nocover
27
+ """
28
+ Get input with preset default and/or tab completion of options
29
+
30
+ Parameters:
31
+
32
+ prompt:str - string to output before requesting input, same as in the
33
+ input() function
34
+
35
+ default:str - value with which to prepopulate the response, can be cleared
36
+ with ctrl-a, ctrl-k
37
+
38
+ options:list - list of choices for tab completion
39
+ """
40
+
41
+ # Clean out the options
42
+ options = [o.strip() + " " for o in options] if options else []
43
+
44
+ # Create the completer using the options
45
+ def complete(text, state):
46
+ results = [x for x in options if x.startswith(text)] + [None]
47
+ return results[state]
48
+ readline.set_completer(complete)
49
+
50
+ # Insert the default when we launch
51
+ def start():
52
+ readline.insert_text(default)
53
+ readline.set_startup_hook(start)
54
+
55
+ # Actually perform the input
56
+ try:
57
+ value = input(prompt)
58
+ finally:
59
+ readline.set_startup_hook()
60
+ readline.set_completer(null_complete)
61
+ return value.strip()
62
+
63
+
64
+ def tryit(): # pragma: nocover
65
+ """Quick and dirty tester function"""
66
+ return rlinput(prompt='>',
67
+ options=['a-b', 'a-c', 'b-a'])
68
+
69
+
70
+ if __name__ == '__main__':
71
+ tryit()
@@ -0,0 +1,8 @@
1
+
2
+ class SuperWrapper:
3
+ @classmethod
4
+ def wrap(parent_class, method):
5
+ def wrapper(self, *args, **kwargs):
6
+ parent_method = getattr(parent_class, method.__name__)
7
+ return parent_method(self, method, *args, **kwargs)
8
+ return wrapper
@@ -0,0 +1,141 @@
1
+ Metadata-Version: 2.1
2
+ Name: wizlib
3
+ Version: 0.0.0
4
+ Summary: Framework for flexible and powerful command-line applications
5
+ License: MIT
6
+ Author: Steampunk Wizard
7
+ Author-email: wizlib@steampunkwizard.ca
8
+ Requires-Python: >=3.11,<3.12
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Requires-Dist: PyYAML (>=6.0.1,<7.0.0)
13
+ Requires-Dist: gnureadline (>=8.1.2,<9.0.0) ; sys_platform == "darwin"
14
+ Requires-Dist: myst-parser (>=2.0.0,<3.0.0)
15
+ Requires-Dist: pyreadline3 (>=3.4.1,<4.0.0) ; sys_platform == "win32"
16
+ Description-Content-Type: text/markdown
17
+
18
+ # WizLib
19
+
20
+ Framework for flexible and powerful command-line applications
21
+
22
+ ## ClassFamily
23
+
24
+ A class family is a set of class definitions that use single inheritance
25
+ (each subclass inherits from only one parent) and often multiple inheritance
26
+ (subclasses can inherit from subclasses). So it's a hierarchy of classes,
27
+ with one super-parent (termed the "atriarch") at the top.
28
+
29
+ We offer a way for members of the family to declare themselves simply by
30
+ living in the right package location. Then those classes can be instantiated
31
+ using keys or names, without having to be specifically called. The members
32
+ act independently of each other.
33
+
34
+ What we get, after importing everything and loading it all, is essentially a
35
+ little database of classes, where class-level properties become keys for
36
+ looking up member classes. So, for example, we can have a family of commands,
37
+ and use a command string to look up the right command.
38
+
39
+ Ultimately, the atriarch of the family -- the class at the top of the
40
+ hierarchy -- holds the database, actually a list, in the property called
41
+ "family". So that class can be queried to find appropriate family member
42
+ classes or instances thereof.
43
+
44
+ This utility provides functions for importing family members, loading the
45
+ "families" property of the super-parent, and querying the family.
46
+
47
+ In the process of loading and querying the class family, we need to *avoid*
48
+ inheritance of attributes. There might be abstract intermediary classes that
49
+ don't want to play. So we use `__dict__` to ensure we're only seeing the
50
+ atttributes that are defined on that specific class.
51
+
52
+ ## SuperWrapper
53
+
54
+ Provide a decorator to wrap a method so that it's called within the inherited
55
+ version of that method.
56
+
57
+ Example of use:
58
+
59
+ ```python
60
+ class Parent(SuperWrapper):
61
+ def execute(self, method, *args, **kwargs):
62
+ print(f"Parent execute before")
63
+ method(self, *args, **kwargs)
64
+ print(f"Parent execute after")
65
+
66
+ class InBetween(Parent):
67
+ @Parent.wrap
68
+ def execute(self, method, *args, **kwargs):
69
+ print(f"IB execute before")
70
+ method(self, *args, **kwargs)
71
+ print(f"IB execute after")
72
+
73
+ class NewChild(InBetween):
74
+ @InBetween.wrap
75
+ def execute(self, name):
76
+ print(f"Hello {name}")
77
+
78
+ c = NewChild()
79
+ c.execute("Jane")
80
+ ```
81
+
82
+ Note that for a method to be "wrappable" it must take the form shown above, and explicitly call the method that's handed into it. So strictly, this is different from regular inheritance, where the parent class method has the same signature as the child class method.
83
+
84
+ ## RLInput
85
+
86
+ Python supports the GNU readline approach, which enables tab completion, key mappings, and history with the `input()` function. But the documentation is cryptic, and the implementation differs between Linux and MacOS. RLInput makes it easy.
87
+
88
+ ```python
89
+ from wizlib.rlinput import rlinput
90
+ ```
91
+
92
+ It's just a function, with up to three parameters:
93
+
94
+ - `intro:str=""` - The intro or prompt, same as in the `input()` function.
95
+ - `default:str=""` - If provided, the text will be inserted into the buffer at the start, with the cursor at the end of the buffer. So that becomes the default, that must be overridden by the user if they want different input.
96
+ - `options:list=[]` - A list of options for tab completion. This assumes the options are choices for the entire entry; it's not context-dependent within the buffer.
97
+
98
+ Emacs keys are enabled by default; I'm able to use the arrow keys on my Mac so you should too. I made one change to the keyboard mappings, which is the Ctrl-A, instead of just moving the cursor to the beginning of the line, wipes the entire buffer. So to wipe out the default value and type or tab something new, just hit Ctrl-A.
99
+
100
+
101
+ ## The WizApp framework
102
+
103
+ _Docs in progress - might be out of date_
104
+
105
+ Commands automatically handle input via stdin for non-tty inputs such as pipes. Some details:
106
+
107
+ - The argument name is `input`
108
+ - Therefore `input` is a reserved name for arguments
109
+ - Optionally use the `--input` or `-i` command line argument to pass in the same information
110
+ - Reading from stdin in tty cases (i.e. in the terminal) would still have to happen in the command itself.
111
+
112
+ ### ConfigHandler
113
+
114
+ Enables easy configuration across multiple levels. Tries each of the following approaches in order until one finds the required config option
115
+
116
+ - First look for attributes of the instance (subclass of ConfigHandler) itself (e.g. `gitlab_host`)
117
+ - Then look for a specific env variable for that config setting in all caps, e.g. `GITLAB_HOST`
118
+ - If those both fail, then look for a YAML configuration file:
119
+ - First identified with a `--config` / `-c` option on the command line
120
+ - Then with a path in the `APPNAME_CONFIG` environment variable - note all caps
121
+ - Then look in the local working directory for `.appname.yml`
122
+ - Then look for `~/.appname.yml` in the user's home directory
123
+
124
+ Config files are in YAML, and look something like this:
125
+
126
+ ```yaml
127
+ gitlab:
128
+ host: gitlab.com
129
+ local:
130
+ root: $HOME/git
131
+ ```
132
+
133
+ Note that nested labels in the config map to hyphenated command line options.
134
+
135
+
136
+ ---
137
+
138
+ Logo by [Freepik](https://www.freepik.com/?_gl=1*1y9rvc9*test_ga*Mjc1MTIzODYxLjE2ODA3OTczNTg.*test_ga_523JXC6VL7*MTY4MDc5NzM1OC4xLjEuMTY4MDc5NzQxNS4zLjAuMA..*fp_ga*Mjc1MTIzODYxLjE2ODA3OTczNTg.*fp_ga_1ZY8468CQB*MTY4MDc5NzM1OC4xLjEuMTY4MDc5NzQxNS4zLjAuMA..)
139
+
140
+
141
+
@@ -0,0 +1,14 @@
1
+ wizlib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ wizlib/app.py,sha256=7emrVV077IXk7PGQeFz1jne0jpDCPyrJXrZfaf2Ya54,2822
3
+ wizlib/class_family.py,sha256=EX1ZZmrmC2M-PV_iorHXCWqETE2thrwQE45RtHxojbs,4424
4
+ wizlib/command.py,sha256=GQlXP8dOJHXUGDhjV3VYnOmHLtbEc4TMAGogLUqy2U8,1304
5
+ wizlib/config_handler.py,sha256=alHpJzFY39XXDv94W4fLwda6hjruySADAJD944rE3HU,2639
6
+ wizlib/error.py,sha256=ypwdMOYhtgKWd48ccfOX8idmCXmm-Skwx3gkPwqJB3c,46
7
+ wizlib/handler.py,sha256=vUrlMIER019Sz17yeg95Cs6u9Vo5u24qf0q7p9HEWec,306
8
+ wizlib/input_handler.py,sha256=RmoZA_FlQ_1qeDEV9YZSU8Zw933pMPEb4yOZLPUGnBA,597
9
+ wizlib/parser.py,sha256=O34azN4ttVfwwAsza0hujxGxDpzc4xUEVAf26DXJS5g,1505
10
+ wizlib/rlinput.py,sha256=l00Pa3rxNeY6LJgz8Aws_rTKoEchw33fuL8yqHF9_-o,1754
11
+ wizlib/super_wrapper.py,sha256=F834ytHqA7zegTD1ezk_uxlF9PLygh84wReuiqcI7BI,272
12
+ wizlib-0.0.0.dist-info/METADATA,sha256=Sz-Eh8FH_CgeAMbZ1FDNLUDHRrsrs-E5MfXT8RNOAog,5965
13
+ wizlib-0.0.0.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
14
+ wizlib-0.0.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: poetry-core 1.8.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any