5 ------------------------------------------------------------------------------
6 This file is part of grepros - grep for ROS bag files and live topics.
7 Released under the BSD License.
12 ------------------------------------------------------------------------------
18 except ImportError:
import Queue
as queue
23 from ...
import common
25 from ... common
import ConsolePrinter, MatchMarkers, plural
26 from ... outputs
import RolloverSinkMixin, Sink, TextSinkMixin
27 from ... vendor
import step
31 """Writes messages to an HTML file."""
34 FILE_EXTENSIONS = (
".htm",
".html")
37 TEMPLATE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)),
"html.tpl")
43 DEFAULT_ARGS = dict(META=
False, WRITE_OPTIONS={}, HIGHLIGHT=
True, MATCH_WRAPPER=
None,
44 ORDERBY=
None, VERBOSE=
False, COLOR=
True, EMIT_FIELD=(), NOEMIT_FIELD=(),
45 MAX_FIELD_LINES=
None, START_LINE=
None, END_LINE=
None,
46 MAX_MESSAGE_LINES=
None, LINES_AROUND_MATCH=
None, MATCHED_FIELDS_ONLY=
False,
51 @param args arguments as namespace or dictionary, case-insensitive;
52 or a single path as the name of HTML file to write
53 @param args.write name of HTML file to write,
54 will add counter like .2 to filename if exists
55 @param args.write_options ```
56 {"template": path to custom HTML template, if any,
57 "overwrite": whether to overwrite existing file
59 "rollover-size": bytes limit for individual output files,
60 "rollover-count": message limit for individual output files,
61 "rollover-duration": time span limit for individual output files,
62 as ROS duration or convertible seconds,
63 "rollover-template": output filename template, supporting
64 strftime format codes like "%H-%M-%S"
65 and "%(index)s" as output file index}
67 @param args.highlight highlight matched values (default true)
68 @param args.orderby "topic" or "type" if any to group results by
69 @param args.color False or "never" for not using colors in replacements
70 @param args.emit_field message fields to emit if not all
71 @param args.noemit_field message fields to skip in output
72 @param args.max_field_lines maximum number of lines to output per field
73 @param args.start_line message line number to start output from
74 @param args.end_line message line number to stop output at
75 @param args.max_message_lines maximum number of lines to output per message
76 @param args.lines_around_match number of message lines around matched fields to output
77 @param args.matched_fields_only output only the fields where match was found
78 @param args.wrap_width character width to wrap message YAML output at
79 @param args.match_wrapper string to wrap around matched values,
80 both sides if one value, start and end if more than one,
81 or no wrapping if zero values
82 @param args.meta whether to emit metainfo
83 @param args.verbose whether to emit debug information
84 @param kwargs any and all arguments as keyword overrides,
87 args = {
"WRITE": str(args)}
if isinstance(args, common.PATH_TYPES)
else args
88 args = common.ensure_namespace(args, HtmlSink.DEFAULT_ARGS, **kwargs)
90 args.COLOR = bool(args.HIGHLIGHT)
93 RolloverSinkMixin.__init__(self, args)
94 TextSinkMixin.__init__(self, args)
97 self.
_overwrite = (args.WRITE_OPTIONS.get(
"overwrite")
in (
True,
"true"))
101 WRAPS = ((args.MATCH_WRAPPER
or [
""]) * 2)[:2]
102 START = (
'<span class="match">' + step.escape_html(WRAPS[0]))
if args.HIGHLIGHT
else ""
103 END = (step.escape_html(WRAPS[1]) +
'</span>')
if args.HIGHLIGHT
else ""
105 MatchMarkers.END: END,
106 ConsolePrinter.STYLE_LOWLIGHT:
'<span class="lowlight">',
107 ConsolePrinter.STYLE_RESET:
'</span>'}
111 atexit.register(self.
close)
113 def emit(self, topic, msg, stamp=None, match=None, index=None):
114 """Writes message to output file."""
115 if not self.
validate():
raise Exception(
"invalid")
117 RolloverSinkMixin.ensure_rollover(self, topic, msg, stamp)
118 self.
_queue.put((topic, msg, stamp, match, index))
127 Returns whether write options are valid and ROS environment is set and file is writable,
131 result = RolloverSinkMixin.validate(self)
132 if self.
args.WRITE_OPTIONS.get(
"template")
and not os.path.isfile(self.
_template_path):
134 ConsolePrinter.error(
"Template does not exist: %s.", self.
_template_path)
135 if self.
args.WRITE_OPTIONS.get(
"overwrite")
not in (
None,
True,
False,
"true",
"false"):
136 ConsolePrinter.error(
"Invalid overwrite option for HTML: %r. "
137 "Choose one of {true, false}.",
138 self.
args.WRITE_OPTIONS[
"overwrite"])
140 if not common.verify_io(self.
args.WRITE,
"w"):
142 self.
valid = api.validate()
and result
146 """Closes output file, if any, emits metainfo."""
152 super(HtmlSink, self).
close()
155 """Closes output file, if any."""
159 writer.is_alive()
and writer.join()
162 """Writes out any pending data to disk."""
166 """Returns message as formatted string, optionally highlighted for matches if configured."""
167 text = TextSinkMixin.format_message(self, msg, self.
args.HIGHLIGHT
and highlight)
168 text =
"".join(self.
_tag_repls.get(x)
or step.escape_html(x)
173 """Returns True if sink is configured to highlight matched values."""
174 return bool(self.
args.HIGHLIGHT)
177 """Writer-loop, streams HTML template to file."""
183 template = step.Template(tpl, escape=
True, strip=
False, postprocess=convert_lf)
184 ns = dict(source=self.
source, sink=self, messages=self.
_produce(),
185 args=
None, timeline=
not self.
args.ORDERBY)
186 if main.CLI_ARGS: ns.update(args=main.CLI_ARGS)
188 if self.
args.VERBOSE:
190 action =
"Overwriting" if sz
and self.
_overwrite else "Creating"
191 ConsolePrinter.debug(
"%s HTML output %s.", action, self.
filename)
192 common.makedirs(os.path.dirname(self.
filename))
193 with open(self.
filename,
"wb")
as f:
194 template.stream(f, ns, buffer_size=0)
195 except Exception
as e:
201 """Yields messages from emit queue, as (topic, msg, stamp, match, index)."""
207 (topic, msg, stamp, match, index) = entry
208 topickey = api.TypeMeta.make(msg, topic).topickey
209 if self.
args.VERBOSE
and topickey
not in self.
_counts:
210 ConsolePrinter.debug(
"Adding topic %s in HTML output.", topic)
212 super(HtmlSink, self).
emit(topic, msg, stamp, match, index)
215 while self.
_queue.get_nowait()
or True: self.
_queue.task_done()
216 except queue.Empty:
pass
220 r"""Returns string with \r \n \r\n linefeeds replaced with given."""
221 return re.sub(
"(\r(?!\n))|((?<!\r)\n)|(\r\n)", newline, s)
226 """Adds HTML output format support."""
227 from ...
import plugins
228 plugins.add_write_format(
"html", HtmlSink,
"HTML", [
229 (
"template=/my/path.tpl",
"custom template to use for HTML output"),
230 (
"overwrite=true|false",
"overwrite existing file in HTML output\n"
231 "instead of appending unique counter (default false)")
232 ] + RolloverSinkMixin.get_write_options(
"HTML"))
233 plugins.add_output_label(
"HTML", [
"--emit-field",
"--no-emit-field",
"--matched-fields-only",
234 "--lines-around-match",
"--lines-per-field",
"--start-line",
235 "--end-line",
"--lines-per-message",
"--match-wrapper"])
238 __all__ = [
"HtmlSink",
"init"]