5Allows specifying custom plugins for "source", "scan" or "sink".
6Auto-inits any plugins in grepros.plugins.auto.
8Supported (but not required) plugin interface methods:
10- `init(args)`: invoked at startup with command-line arguments
11- `load(category, args)`: invoked with category "scan" or "source" or "sink",
12 using returned value if not None
14Plugins are free to modify package internals, like adding command-line arguments
19- `plugins.add_write_format(name, cls, label=
None, options=((name, help), ))`:
20 adds an output plugin to defaults
21- `plugins.add_output_label(label, flags)`:
22 adds plugin label to outputs enumerated
in given argument help texts
23- `plugins.get_argument(name, group=
None)`:
24 returns a command-line argument configuration dictionary,
or None
26------------------------------------------------------------------------------
27This file
is part of grepros - grep
for ROS bag files
and live topics.
28Released under the BSD License.
33------------------------------------------------------------------------------
35## @namespace grepros.plugins
42from .. common import ConsolePrinter, ensure_namespace, get_name, import_item
43from .. outputs import MultiSink
47## {"some.module" or "some.module.Cls": <module 'some.module' from ..> or <class 'some.module.Cls'>}
50## Added output labels to insert into argument texts, as {label: [argument flag, ]}
53## Added write options, as {plugin label: [(name, help), ]}
56## Function argument defaults
57DEFAULT_ARGS = dict(PLUGIN=[], STOP_ON_ERROR=False)
60def init(args=None, **kwargs):
62 Imports and initializes all plugins
from auto
and from given arguments.
64 @param args arguments
as namespace
or dictionary, case-insensitive
65 @param args.plugin list of Python modules
or classes to
import,
66 as [
"my.module",
"other.module.SomeClass", ],
67 or module
or class instances
68 @param args.stop_on_error stop execution on any error like failing to load plugin
69 @param kwargs any
and all arguments
as keyword overrides, case-insensitive
71 args = ensure_namespace(args, DEFAULT_ARGS, **kwargs)
72 for f
in sorted(glob.glob(os.path.join(os.path.dirname(__file__),
"auto",
"*"))):
73 if not f.lower().endswith((
".py",
".pyc")):
continue
74 name = os.path.splitext(os.path.split(f)[-1])[0]
75 if name.startswith(
"__")
or name
in PLUGINS:
continue
77 modulename =
"%s.auto.%s" % (__package__, name)
79 plugin = import_item(modulename)
80 if callable(getattr(plugin,
"init",
None)): plugin.init(args)
81 PLUGINS[name] = plugin
83 ConsolePrinter.error(
"Error loading plugin %s.", modulename)
84 if args.STOP_ON_ERROR:
raise
93 Imports plugin Python packages, invokes init(args) if any, raises on error.
95 @param args arguments
as namespace
or dictionary, case-insensitive
96 @param args.plugin list of Python modules
or classes to
import,
97 as [
"my.module",
"other.module.SomeClass", ],
98 or module
or class instances
99 @param kwargs any
and all arguments
as keyword overrides, case-insensitive
101 args = ensure_namespace(args, DEFAULT_ARGS, **kwargs)
102 for obj
in args.PLUGIN:
103 name = obj
if isinstance(obj, six.string_types)
else get_name(obj)
104 if name
in PLUGINS:
continue
106 plugin = import_item(name)
if isinstance(obj, six.string_types)
else obj
107 if callable(getattr(plugin,
"init",
None)): plugin.init(args)
108 PLUGINS[name] = plugin
109 except ImportWarning:
112 ConsolePrinter.error(
"Error loading plugin %s.", name)
116def load(category, args, collect=False):
118 Returns a plugin category instance loaded from any configured plugin,
or None.
120 @param category item category like
"source",
"sink",
or "scan"
121 @param args arguments
as namespace
or dictionary, case-insensitive
122 @param collect
if true, returns a list of instances,
123 using all plugins that
return something
126 args = ensure_namespace(args)
127 for name, plugin
in PLUGINS.items():
128 if callable(getattr(plugin,
"load",
None)):
130 instance = plugin.load(category, args)
131 if instance
is not None:
132 result.append(instance)
136 ConsolePrinter.error(
"Error invoking %s.load(%r, args).", name, category)
138 return result
if collect
else result[0]
if result
else None
143 Adds plugin label to outputs enumerated in given argument help texts.
145 @param label output label to add, like
"Parquet"
146 @param flags list of argument flags like
"--emit-field" to add the output label to
148 OUTPUT_LABELS.setdefault(label, []).extend(flags)
153 Adds plugin to `--write` in main.ARGUMENTS
and MultiSink formats.
155 @param name format name like
"csv", added to `--write .. format=FORMAT`
156 @param cls
class providing Sink interface
157 @param label plugin label;
if multiple plugins add the same option,
158 "label output" in help text
is replaced
with "label1/label2/.. output"
159 @param options a sequence of (name, help) to add to --write help, like
160 [(
"template=/my/path.tpl",
"custom template to use for HTML output")]
162 MultiSink.FORMAT_CLASSES[name] = cls
163 if options: WRITE_OPTIONS.setdefault(label, []).extend(options)
168 Returns a command-line argument dictionary, or None if not found.
170 @param name argument name like
"--write"
171 @param group argument group like
"Output control",
if any
175 return next((d
for d
in main.ARGUMENTS.get(
"groups", {}).get(group, [])
176 if name
in d.get(
"args")),
None)
177 return next((d
for d
in main.ARGUMENTS.get(
"arguments", [])
178 if name
in d.get(
"args")),
None)
182 """Populates argument texts with added output labels."""
183 if not OUTPUT_LABELS:
return
186 argslist = sum(main.ARGUMENTS.get(
"groups", {}).values(), main.ARGUMENTS[
"arguments"][:])
187 args = {f: x
for x
in argslist
for f
in x[
"args"]}
188 args.update((id(x), x)
for x
in argslist)
192 for label, flag
in ((l, f)
for l, ff
in OUTPUT_LABELS.items()
for f
in ff):
193 if flag
in args: arglabels.setdefault(id(args[flag]), []).append(label)
194 else: ConsolePrinter.warn(
"Unknown command-line flag %r from output %r.", flag, label)
197 for arg, labels
in ((args[x], ll)
for x, ll
in arglabels.items()):
198 match = re.search(
r"(\A.*?\s*in\s)(\S+)(\s+output.*\Z)", arg[
"help"], re.DOTALL)
200 ConsolePrinter.warn(
"Command-line flag %s has no text on output for labels %s.",
201 arg[
"args"],
", ".join(map(repr, sorted(set(labels)))))
203 labels2 = sorted(set(labels + match.group(2).split(
"/")), key=
lambda x: x.lower())
204 arg[
"help"] = match.expand(
r"\1%s\3" %
"/".join(labels2))
206 OUTPUT_LABELS.clear()
210 """Adds known non-auto plugins to `--plugin` argument help."""
212 for f
in sorted(glob.glob(os.path.join(os.path.dirname(__file__),
"*"))):
213 if not f.lower().endswith((
".py",
".pyc")):
continue
214 name = os.path.splitext(os.path.split(f)[-1])[0]
215 if not name.startswith(
"__"):
216 plugins.append(
"%s.%s" % (__package__, name))
219 if pluginarg
and plugins:
221 lines = [
"load a Python module or class as plugin",
"(built-in plugins: "]
222 for i, name
in enumerate(plugins):
223 if not i: lines[-1] += name
225 if len(lines[-1] +
", " + name) > MAXLINELEN:
227 lines.append(
" " + name)
228 else: lines[-1] +=
", " + name
230 pluginarg[
"help"] =
"\n".join(lines)
234 """Populates main.ARGUMENTS with added write formats and options."""
236 if not writearg:
return
238 formats = sorted(set(MultiSink.FORMAT_CLASSES))
239 writearg[
"metavar"] =
"TARGET [format=%s] [KEY=VALUE ...]" %
"|".join(formats)
240 if not WRITE_OPTIONS:
return
251 for label, opts
in WRITE_OPTIONS.items():
252 for name, help
in opts:
253 texts.setdefault(name, help)
254 namelabels.setdefault(name, []).append(label)
255 namelens[name] = len(name)
258 maxname = max(x
if x <= MAXNAME
else 0
for x
in namelens.values())
259 for label, opts
in WRITE_OPTIONS.items():
260 for name, help
in opts:
261 inters[name] =
"\n" if len(name) > MAXNAME
else " " * (maxname - len(name) + 2)
262 indent = LEADING +
" " +
" " * (maxname
or MAXNAME)
265 PLACEHOLDER =
"<plugin label replacement>"
266 for name
in list(texts):
267 if len(namelabels[name]) > 1:
268 for label
in namelabels[name]:
269 texts[name] = texts[name].replace(
"%s output" % label, PLACEHOLDER)
270 labels =
"/".join(sorted(filter(bool, namelabels[name]), key=
lambda x: x.lower()))
271 texts[name] = texts[name].replace(PLACEHOLDER, labels +
" output")
273 fmt =
lambda n, h:
"\n".join((indent
if i
or "\n" == inters[n]
else "") + l
274 for i, l
in enumerate(h.splitlines()))
275 text =
"\n".join(sorted(
"".join((LEADING, n, inters[n], fmt(n, h)))
276 for n, h
in texts.items()))
277 writearg[
"help"] +=
"\n" + text
279 WRITE_OPTIONS.clear()
285 "PLUGINS",
"init",
"configure",
"load",
"add_write_format",
"get_argument",
286 "populate_known_plugins",
"populate_write_formats",
Combines any number of sinks.
configure(args=None, **kwargs)
Imports plugin Python packages, invokes init(args) if any, raises on error.
load(category, args, collect=False)
Returns a plugin category instance loaded from any configured plugin, or None.
add_output_label(label, flags)
Adds plugin label to outputs enumerated in given argument help texts.
populate_write_formats()
Populates main.ARGUMENTS with added write formats and options.
get_argument(name, group=None)
Returns a command-line argument dictionary, or None if not found.
populate_output_arguments()
Populates argument texts with added output labels.
init(args=None, **kwargs)
Imports and initializes all plugins from auto and from given arguments.
populate_known_plugins()
Adds known non-auto plugins to –plugin argument help.
add_write_format(name, cls, label=None, options=())
Adds plugin to –write in main.ARGUMENTS and MultiSink formats.