gearbox 0.3.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.
gearbox/__init__.py ADDED
File without changes
gearbox/command.py ADDED
@@ -0,0 +1,46 @@
1
+ import argparse
2
+ import inspect
3
+ import os
4
+ import sys
5
+
6
+ from .template import GearBoxTemplate
7
+
8
+
9
+ class Command(object):
10
+ deprecated = False
11
+
12
+ def __init__(self, app, app_args, cmd_name=None):
13
+ self.app = app
14
+ self.app_args = app_args
15
+ self.cmd_name = cmd_name
16
+
17
+ def get_description(self):
18
+ """Override to provide custom description for command."""
19
+ return inspect.getdoc(self.__class__) or ""
20
+
21
+ def get_parser(self, prog_name):
22
+ """Override to add command options."""
23
+ parser = argparse.ArgumentParser(
24
+ description=self.get_description(), prog=prog_name, add_help=False
25
+ )
26
+ return parser
27
+
28
+ def take_action(self, parsed_args):
29
+ """Override to do something useful."""
30
+ raise NotImplementedError
31
+
32
+ def run(self, parsed_args):
33
+ self.take_action(parsed_args)
34
+ return 0
35
+
36
+
37
+ class TemplateCommand(Command):
38
+ template = GearBoxTemplate()
39
+
40
+ def get_template_path(self):
41
+ module = sys.modules[self.__class__.__module__]
42
+ module_path = module.__file__
43
+ return os.path.join(os.path.abspath(os.path.dirname(module_path)), "template")
44
+
45
+ def run_template(self, output_dir, opts):
46
+ self.template.run(self.get_template_path(), output_dir, vars(opts))
@@ -0,0 +1,82 @@
1
+ # -*- coding: utf-8 -*-
2
+ """Discover and lookup command plugins.
3
+
4
+ This comes from OpenStack cliff.
5
+ """
6
+
7
+ import importlib.metadata
8
+ import logging
9
+
10
+ LOG = logging.getLogger(__name__)
11
+
12
+
13
+ class EntryPointWrapper(object):
14
+ """Wrap up a command class already imported to make it look like a plugin."""
15
+
16
+ def __init__(self, name, command_class):
17
+ self.name = name
18
+ self.command_class = command_class
19
+
20
+ def load(self, require=False):
21
+ return self.command_class
22
+
23
+
24
+ class CommandManager(object):
25
+ """Discovers commands and handles lookup based on argv data.
26
+
27
+ :param namespace: String containing the setuptools entrypoint namespace
28
+ for the plugins to be loaded. For example,
29
+ ``'cliff.formatter.list'``.
30
+ :param convert_underscores: Whether cliff should convert underscores to
31
+ spaces in entry_point commands.
32
+ """
33
+
34
+ def __init__(self, namespace, convert_underscores=True):
35
+ self.commands = {}
36
+ self.namespace = namespace
37
+ self.convert_underscores = convert_underscores
38
+ self._load_commands()
39
+
40
+ def _load_commands(self):
41
+ # NOTE(jamielennox): kept for compatability.
42
+ self.load_commands(self.namespace)
43
+
44
+ def load_commands(self, namespace):
45
+ """Load all the commands from an entrypoint"""
46
+ entry_points = importlib.metadata.entry_points()
47
+ if hasattr(entry_points, "select"):
48
+ entry_points = entry_points.select(group=namespace)
49
+ else:
50
+ entry_points = entry_points.get(namespace, [])
51
+ for ep in entry_points:
52
+ LOG.debug("found command %r", ep.name)
53
+ cmd_name = (
54
+ ep.name.replace("_", " ") if self.convert_underscores else ep.name
55
+ )
56
+ self.commands[cmd_name] = ep
57
+ return
58
+
59
+ def __iter__(self):
60
+ return iter(self.commands.items())
61
+
62
+ def add_command(self, name, command_class):
63
+ self.commands[name] = EntryPointWrapper(name, command_class)
64
+
65
+ def find_command(self, argv):
66
+ """Given an argument list, find a command and
67
+ return the processor and any remaining arguments.
68
+ """
69
+ search_args = argv[:]
70
+ name = ""
71
+ while search_args:
72
+ if search_args[0].startswith("-"):
73
+ name = "%s %s" % (name, search_args[0])
74
+ raise ValueError("Invalid command %r" % name)
75
+ next_val = search_args.pop(0)
76
+ name = "%s %s" % (name, next_val) if name else next_val
77
+ if name in self.commands:
78
+ cmd_ep = self.commands[name]
79
+ cmd_factory = cmd_ep.load()
80
+ return (cmd_factory, name, search_args)
81
+ else:
82
+ raise ValueError("Unknown command %r" % next(iter(argv), ""))
File without changes
@@ -0,0 +1,3 @@
1
+ from .command import MakePackageCommand
2
+
3
+ __all__ = ["MakePackageCommand"]
@@ -0,0 +1,99 @@
1
+ from __future__ import print_function
2
+
3
+ import re
4
+
5
+ from gearbox.command import TemplateCommand
6
+
7
+
8
+ class MakePackageCommand(TemplateCommand):
9
+ CLEAN_PACKAGE_NAME_RE = re.compile("[^a-zA-Z0-9_]")
10
+
11
+ def get_description(self):
12
+ return "Creates a basic python package"
13
+
14
+ def get_parser(self, prog_name):
15
+ parser = super(MakePackageCommand, self).get_parser(prog_name)
16
+
17
+ parser.add_argument(
18
+ "-n",
19
+ "--name",
20
+ dest="project",
21
+ metavar="NAME",
22
+ required=True,
23
+ help="Project Name",
24
+ )
25
+
26
+ parser.add_argument(
27
+ "-o",
28
+ "--output-dir",
29
+ dest="output_dir",
30
+ metavar="OUTPUT_DIR",
31
+ help="Destination directory (by default the project name)",
32
+ )
33
+
34
+ parser.add_argument(
35
+ "-p",
36
+ "--package",
37
+ dest="package",
38
+ metavar="PACKAGE",
39
+ help="Python Package Name",
40
+ )
41
+
42
+ parser.add_argument(
43
+ "-a",
44
+ "--author",
45
+ dest="author",
46
+ metavar="AUTHOR",
47
+ default="Unknown",
48
+ help="Name of the package author",
49
+ )
50
+
51
+ parser.add_argument(
52
+ "-e",
53
+ "--email",
54
+ dest="author_email",
55
+ metavar="AUTHOR_EMAIL",
56
+ help="Email of the package author",
57
+ )
58
+
59
+ parser.add_argument(
60
+ "-u", "--url", dest="url", metavar="URL", help="Project homepage"
61
+ )
62
+
63
+ parser.add_argument(
64
+ "-l",
65
+ "--license",
66
+ dest="license_name",
67
+ metavar="LICENSE_NAME",
68
+ help="License used for the project",
69
+ )
70
+
71
+ parser.add_argument(
72
+ "-d",
73
+ "--description",
74
+ dest="description",
75
+ metavar="DESCRIPTION",
76
+ help="Package description",
77
+ )
78
+
79
+ parser.add_argument(
80
+ "-k",
81
+ "--keywords",
82
+ dest="keywords",
83
+ metavar="KEYWORDS",
84
+ help="Package keywords",
85
+ )
86
+
87
+ return parser
88
+
89
+ def take_action(self, opts):
90
+ if opts.package is None:
91
+ opts.package = self.CLEAN_PACKAGE_NAME_RE.sub("", opts.project.lower())
92
+
93
+ if opts.output_dir is None:
94
+ opts.output_dir = opts.project
95
+
96
+ opts.zip_safe = False
97
+ opts.version = "0.0.1"
98
+
99
+ self.run_template(opts.output_dir, opts)
@@ -0,0 +1,2 @@
1
+ global-exclude *.pyc
2
+
@@ -0,0 +1,17 @@
1
+ About {{package}}
2
+ -------------------------
3
+
4
+ {{package}} is a Python package created using the gearbox makepackage command.
5
+
6
+ Installing
7
+ -------------------------------
8
+
9
+ {{package}} can be installed from pypi::
10
+
11
+ easy_install {{package}}
12
+
13
+ or::
14
+
15
+ pip install {{package}}
16
+
17
+ should just work for most of the users
File without changes
@@ -0,0 +1,31 @@
1
+ from setuptools import setup, find_packages
2
+ import sys, os
3
+
4
+ here = os.path.abspath(os.path.dirname(__file__))
5
+ try:
6
+ README = open(os.path.join(here, 'README.rst')).read()
7
+ except IOError:
8
+ README = ''
9
+
10
+ version = "{{version}}"
11
+
12
+ setup(name={{repr(project)}},
13
+ version=version,
14
+ description="{{description or ''}}",
15
+ long_description=README,
16
+ classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
17
+ keywords={{repr(keywords or '')}},
18
+ author={{repr(author or '')}},
19
+ author_email={{repr(author_email or '')}},
20
+ url={{repr(url or '')}},
21
+ license={{repr(license_name or '')}},
22
+ packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
23
+ include_package_data=True,
24
+ zip_safe=False,
25
+ install_requires=[
26
+ # -*- Extra requirements: -*-
27
+ ],
28
+ entry_points="""
29
+ # -*- Entry points: -*-
30
+ """,
31
+ )
@@ -0,0 +1,85 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import argparse
4
+ import sys
5
+ import traceback
6
+
7
+ from ..command import Command
8
+
9
+
10
+ class HelpAction(argparse.Action):
11
+ """Provide a custom action so the -h and --help options
12
+ to the main app will print a list of the commands.
13
+ The commands are determined by checking the CommandManager
14
+ instance, passed in as the "default" value for the action.
15
+ """
16
+
17
+ def __call__(self, parser, namespace, values, option_string=None):
18
+ app = self.default
19
+ parser.print_help(sys.stdout)
20
+ print("\nCommands:")
21
+ command_manager = app.command_manager
22
+ for name, ep in sorted(command_manager):
23
+ try:
24
+ factory = ep.load()
25
+ except Exception:
26
+ print("Could not load %r" % ep)
27
+ if namespace.debug:
28
+ traceback.print_exc(file=sys.stdout)
29
+ continue
30
+ try:
31
+ cmd = factory(app, None)
32
+ if cmd.deprecated:
33
+ continue
34
+ except Exception as err:
35
+ print("Could not instantiate %r: %s\n" % (ep, err))
36
+ if namespace.debug:
37
+ traceback.print_exc(file=sys.stdout)
38
+ continue
39
+ one_liner = cmd.get_description().split("\n")[0]
40
+ print(" %-13s %s" % (name, one_liner))
41
+ sys.exit(0)
42
+
43
+
44
+ class HelpCommand(Command):
45
+ """print detailed help for another command"""
46
+
47
+ def get_parser(self, prog_name):
48
+ parser = super(HelpCommand, self).get_parser(prog_name)
49
+ parser.add_argument(
50
+ "cmd",
51
+ nargs="*",
52
+ help="name of the command",
53
+ )
54
+ return parser
55
+
56
+ def take_action(self, parsed_args):
57
+ if not parsed_args.cmd:
58
+ action = HelpAction(None, None, default=self.app)
59
+ action(self.app.parser, self.app.parser, None, None)
60
+ return 1
61
+
62
+ try:
63
+ the_cmd = self.app.command_manager.find_command(
64
+ parsed_args.cmd,
65
+ )
66
+ cmd_factory, cmd_name, search_args = the_cmd
67
+ except ValueError:
68
+ # Did not find an exact match
69
+ cmd = parsed_args.cmd[0]
70
+ fuzzy_matches = [
71
+ k[0] for k in self.app.command_manager if k[0].startswith(cmd)
72
+ ]
73
+ if not fuzzy_matches:
74
+ raise
75
+ print('Command "%s" matches:' % cmd)
76
+ for fm in sorted(fuzzy_matches):
77
+ print(" %s" % fm)
78
+ return
79
+
80
+ self.app_args.cmd = search_args
81
+ cmd = cmd_factory(self.app, self.app_args)
82
+ full_name = " ".join([self.app.NAME, cmd_name])
83
+ cmd_parser = cmd.get_parser(full_name)
84
+ cmd_parser.print_help(sys.stdout)
85
+ return 0
@@ -0,0 +1,160 @@
1
+ from __future__ import print_function
2
+
3
+ import fnmatch
4
+ import os
5
+ import re
6
+ from argparse import RawDescriptionHelpFormatter
7
+
8
+ from gearbox.command import Command
9
+
10
+
11
+ class PatchCommand(Command):
12
+ def get_description(self):
13
+ return r"""Patches files by replacing, appending or deleting text.
14
+
15
+ This is meant to provide a quick and easy way to replace text and
16
+ code in your projects.
17
+
18
+ Here are a few examples, this will replace all xi:include occurrences
19
+ with py:extends in all the template files recursively:
20
+
21
+ $ gearbox patch -R '*.html' xi:include -r py:extends
22
+
23
+ It is also possible to rely on regex and python for more complex
24
+ replacements, like updating the Copyright year in your documentation:
25
+
26
+ $ gearbox patch -R '*.rst' -x 'Copyright(\s*)(\d+)' -e -r '"Copyright\\g<1>"+__import__("datetime").datetime.utcnow().strftime("%Y")'
27
+
28
+ Works on a line by line basis, so it is not possible to match text
29
+ across multiple lines.
30
+ """
31
+
32
+ def get_parser(self, prog_name):
33
+ parser = super(PatchCommand, self).get_parser(prog_name)
34
+ parser.formatter_class = RawDescriptionHelpFormatter
35
+
36
+ parser.add_argument(
37
+ "pattern", help="The glob pattern of files that should be matched"
38
+ )
39
+
40
+ parser.add_argument(
41
+ "text", help="text that should be looked up in matched files."
42
+ )
43
+
44
+ parser.add_argument(
45
+ "-r",
46
+ "--replace",
47
+ dest="replacement",
48
+ help="Replace occurrences of text with REPLACEMENT",
49
+ )
50
+
51
+ parser.add_argument(
52
+ "-a",
53
+ "--append",
54
+ dest="addition",
55
+ help="Append ADDITION after the line with matching text.",
56
+ )
57
+
58
+ parser.add_argument(
59
+ "-d", "--delete", action="store_true", help="Delete lines matching text."
60
+ )
61
+
62
+ parser.add_argument(
63
+ "-x",
64
+ "--regex",
65
+ dest="regex",
66
+ action="store_true",
67
+ help="Parse the text as a regular expression.",
68
+ )
69
+
70
+ parser.add_argument(
71
+ "-R",
72
+ "--recursive",
73
+ dest="recursive",
74
+ action="store_true",
75
+ help="Look for files matching pattern in subfolders too.",
76
+ )
77
+
78
+ parser.add_argument(
79
+ "-e",
80
+ "--eval",
81
+ dest="eval",
82
+ action="store_true",
83
+ help="Eval the replacement as Python code before applying it.",
84
+ )
85
+
86
+ return parser
87
+
88
+ def _walk_recursive(self):
89
+ for root, dirnames, filenames in os.walk(os.getcwd()):
90
+ for filename in filenames:
91
+ yield os.path.join(root, filename)
92
+
93
+ def _walk_flat(self):
94
+ root = os.getcwd()
95
+ for filename in os.listdir(root):
96
+ yield os.path.join(root, filename)
97
+
98
+ def _replace_regex(self, line, text, replacement):
99
+ return re.sub(text, replacement, line)
100
+
101
+ def _replace_plain(self, line, text, replacement):
102
+ return line.replace(text, replacement)
103
+
104
+ def _match_regex(self, line, text):
105
+ return re.search(text, line) is not None
106
+
107
+ def _match_plain(self, line, text):
108
+ return text in line
109
+
110
+ def take_action(self, opts):
111
+ walk = self._walk_flat
112
+ if opts.recursive:
113
+ walk = self._walk_recursive
114
+
115
+ match = self._match_plain
116
+ if opts.regex:
117
+ match = self._match_regex
118
+
119
+ replace = self._replace_plain
120
+ if opts.regex:
121
+ replace = self._replace_regex
122
+
123
+ matches = []
124
+ for filepath in walk():
125
+ if fnmatch.fnmatch(filepath, opts.pattern):
126
+ matches.append(filepath)
127
+
128
+ print("%s files matching" % len(matches))
129
+ for filepath in matches:
130
+ replacement = opts.replacement
131
+ if opts.eval and replacement:
132
+ replacement = str(eval(replacement, globals()))
133
+
134
+ addition = opts.addition
135
+ if opts.eval and addition:
136
+ addition = str(eval(addition, globals()))
137
+
138
+ matches = False
139
+ lines = []
140
+ with open(filepath) as f:
141
+ for line in f:
142
+ if not match(line, opts.text):
143
+ lines.append(line)
144
+ continue
145
+
146
+ matches = True
147
+ empty_line = not line.strip()
148
+ if opts.replacement:
149
+ line = replace(line, opts.text, replacement)
150
+
151
+ if empty_line or line.strip() and not opts.delete:
152
+ lines.append(line)
153
+
154
+ if opts.addition:
155
+ lines.append(addition + "\n")
156
+
157
+ print("%s Patching %s" % (matches and "!" or "x", filepath))
158
+ if matches:
159
+ with open(filepath, "w") as f:
160
+ f.writelines(lines)