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)
95 self.
_queue = queue.Queue()
105 def emit(self, topic, msg, stamp=None, match=None, index=None):
106 """Writes message to output file."""
109 RolloverSinkMixin.ensure_rollover(self, topic, msg, stamp)
110 self.
_queue.put((topic, msg, stamp, match, index))
119 Returns whether write options are valid and ROS environment is set and file is writable,
123 result = all([Sink.validate(self), TextSinkMixin.validate(self)])
124 if not RolloverSinkMixin.validate(self):
126 if self.
args.WRITE_OPTIONS.get(
"template")
and not os.path.isfile(self.
_template_path):
128 ConsolePrinter.error(
"Template does not exist: %s.", self.
_template_path)
129 if self.
args.WRITE_OPTIONS.get(
"overwrite")
not in (
None,
True,
False,
"true",
"false"):
130 ConsolePrinter.error(
"Invalid overwrite option for HTML: %r. "
131 "Choose one of {true, false}.",
132 self.
args.WRITE_OPTIONS[
"overwrite"])
134 if not common.verify_io(self.
args.WRITE,
"w"):
138 self.
_overwrite = (self.
args.WRITE_OPTIONS.get(
"overwrite")
in (
True,
"true"))
141 WRAPS, START = ((self.
args.MATCH_WRAPPER
or [
""]) * 2)[:2],
""
142 if self.
args.HIGHLIGHT: START = (
'<span class="match">' + step.escape_html(WRAPS[0]))
143 END = (step.escape_html(WRAPS[1]) +
'</span>')
if self.
args.HIGHLIGHT
else ""
145 MatchMarkers.END: END,
146 ConsolePrinter.STYLE_LOWLIGHT:
'<span class="lowlight">',
147 ConsolePrinter.STYLE_RESET:
'</span>'}
154 """Closes output file, if any, emits metainfo."""
160 super(HtmlSink, self).
close()
163 """Closes output file, if any."""
167 writer.is_alive()
and writer.join()
170 """Writes out any pending data to disk."""
174 """Returns message as formatted string, optionally highlighted for matches if configured."""
175 text = TextSinkMixin.format_message(self, msg, self.
args.HIGHLIGHT
and highlight)
176 text =
"".join(self.
_tag_repls.get(x)
or step.escape_html(x)
181 """Returns True if sink is configured to highlight matched values."""
182 return bool(self.
args.HIGHLIGHT)
185 """Writer-loop, streams HTML template to file."""
190 with open(self.
_template_path, encoding=
"utf-8")
as f: tpl = f.read()
191 template = step.Template(tpl, escape=
True, strip=
False, postprocess=convert_lf)
192 ns = dict(source=self.
source, sink=self, messages=self.
_produce(),
193 args=
None, timeline=
not self.
args.ORDERBY)
194 if main.CLI_ARGS: ns.update(args=main.CLI_ARGS)
196 if self.
args.VERBOSE:
198 action =
"Overwriting" if sz
and self.
_overwrite else "Creating"
202 template.stream(f, ns, buffer_size=0)
203 except Exception
as e:
209 """Yields messages from emit queue, as (topic, msg, stamp, match, index)."""
215 (topic, msg, stamp, match, index) = entry
216 topickey = api.TypeMeta.make(msg, topic).topickey
217 if self.
args.VERBOSE
and topickey
not in self.
_counts:
218 ConsolePrinter.debug(
"Adding topic %s in HTML output.", topic)
220 super(HtmlSink, self).
emit(topic, msg, stamp, match, index)
223 while self.
_queue.get_nowait()
or True: self.
_queue.task_done()
224 except queue.Empty:
pass