Skip to content

Commit 998615a

Browse files
authored
Adding support for gzipped ASC logging file (.asc.gz) (#1138)
1 parent 78c0db3 commit 998615a

File tree

6 files changed

+108
-9
lines changed

6 files changed

+108
-9
lines changed

can/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
)
2626

2727
from .io import Logger, SizedRotatingLogger, Printer, LogReader, MessageSync
28-
from .io import ASCWriter, ASCReader
28+
from .io import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader
2929
from .io import BLFReader, BLFWriter
3030
from .io import CanutilsLogReader, CanutilsLogWriter
3131
from .io import CSVWriter, CSVReader

can/io/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from .player import LogReader, MessageSync
99

1010
# Format specific
11-
from .asc import ASCWriter, ASCReader
11+
from .asc import ASCWriter, ASCReader, GzipASCWriter, GzipASCReader
1212
from .blf import BLFReader, BLFWriter
1313
from .canutils import CanutilsLogReader, CanutilsLogWriter
1414
from .csv import CSVWriter, CSVReader

can/io/asc.py

+69-2
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
- https://bitbucket.org/tobylorenz/vector_asc/src/47556e1a6d32c859224ca62d075e1efcc67fa690/src/Vector/ASC/tests/unittests/data/CAN_Log_Trigger_3_2.asc?at=master&fileviewer=file-view-default
66
- under `test/data/logfile.asc`
77
"""
8-
9-
from typing import cast, Any, Generator, IO, List, Optional, Dict
8+
import gzip
9+
from typing import cast, Any, Generator, IO, List, Optional, Dict, Union
1010

1111
from datetime import datetime
1212
import time
1313
import logging
1414

15+
from .. import typechecking
1516
from ..message import Message
1617
from ..listener import Listener
1718
from ..util import channel2int
@@ -396,3 +397,69 @@ def on_message_received(self, msg: Message) -> None:
396397
data=" ".join(data),
397398
)
398399
self.log_event(serialized, msg.timestamp)
400+
401+
402+
class GzipASCReader(ASCReader):
403+
"""Gzipped version of :class:`~can.ASCReader`"""
404+
405+
def __init__(
406+
self,
407+
file: Union[typechecking.FileLike, typechecking.StringPathLike],
408+
base: str = "hex",
409+
relative_timestamp: bool = True,
410+
):
411+
"""
412+
:param file: a path-like object or as file-like object to read from
413+
If this is a file-like object, is has to opened in text
414+
read mode, not binary read mode.
415+
:param base: Select the base(hex or dec) of id and data.
416+
If the header of the asc file contains base information,
417+
this value will be overwritten. Default "hex".
418+
:param relative_timestamp: Select whether the timestamps are
419+
`relative` (starting at 0.0) or `absolute` (starting at
420+
the system time). Default `True = relative`.
421+
"""
422+
self._fileobj = None
423+
if file is not None and (hasattr(file, "read") and hasattr(file, "write")):
424+
# file is None or some file-like object
425+
self._fileobj = file
426+
super(GzipASCReader, self).__init__(
427+
gzip.open(file, mode="rt"), base, relative_timestamp
428+
)
429+
430+
def stop(self) -> None:
431+
super(GzipASCReader, self).stop()
432+
if self._fileobj is not None:
433+
self._fileobj.close()
434+
435+
436+
class GzipASCWriter(ASCWriter):
437+
"""Gzipped version of :class:`~can.ASCWriter`"""
438+
439+
def __init__(
440+
self,
441+
file: Union[typechecking.FileLike, typechecking.StringPathLike],
442+
channel: int = 1,
443+
compresslevel: int = 6,
444+
):
445+
"""
446+
:param file: a path-like object or as file-like object to write to
447+
If this is a file-like object, is has to opened in text
448+
write mode, not binary write mode.
449+
:param channel: a default channel to use when the message does not
450+
have a channel set
451+
:param compresslevel: Gzip compresslevel, see
452+
:class:`~gzip.GzipFile` for details. The default is 6.
453+
"""
454+
self._fileobj = None
455+
if file is not None and (hasattr(file, "read") and hasattr(file, "write")):
456+
# file is None or some file-like object
457+
self._fileobj = file
458+
super(GzipASCWriter, self).__init__(
459+
gzip.open(file, mode="wt", compresslevel=compresslevel), channel
460+
)
461+
462+
def stop(self) -> None:
463+
super(GzipASCWriter, self).stop()
464+
if self._fileobj is not None:
465+
self._fileobj.close()

can/io/logger.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from ..message import Message
2222
from ..listener import Listener
2323
from .generic import BaseIOHandler, FileIOMessageWriter
24-
from .asc import ASCWriter
24+
from .asc import ASCWriter, GzipASCWriter
2525
from .blf import BLFWriter
2626
from .canutils import CanutilsLogWriter
2727
from .csv import CSVWriter
@@ -36,6 +36,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
3636
3737
The format is determined from the file format which can be one of:
3838
* .asc: :class:`can.ASCWriter`
39+
* .asc.gz: :class:`can.CompressedASCWriter`
3940
* .blf :class:`can.BLFWriter`
4041
* .csv: :class:`can.CSVWriter`
4142
* .db: :class:`can.SqliteWriter`
@@ -54,6 +55,7 @@ class Logger(BaseIOHandler, Listener): # pylint: disable=abstract-method
5455
fetched_plugins = False
5556
message_writers = {
5657
".asc": ASCWriter,
58+
".asc.gz": GzipASCWriter,
5759
".blf": BLFWriter,
5860
".csv": CSVWriter,
5961
".db": SqliteWriter,
@@ -83,7 +85,7 @@ def __new__( # type: ignore
8385
)
8486
Logger.fetched_plugins = True
8587

86-
suffix = pathlib.PurePath(filename).suffix.lower()
88+
suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes)
8789
try:
8890
return cast(
8991
Listener, Logger.message_writers[suffix](filename, *args, **kwargs)

can/io/player.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
import can
1515

1616
from .generic import BaseIOHandler, MessageReader
17-
from .asc import ASCReader
17+
from .asc import ASCReader, GzipASCReader
1818
from .blf import BLFReader
1919
from .canutils import CanutilsLogReader
2020
from .csv import CSVReader
@@ -27,6 +27,7 @@ class LogReader(BaseIOHandler):
2727
2828
The format is determined from the file format which can be one of:
2929
* .asc
30+
* .asc.gz
3031
* .blf
3132
* .csv
3233
* .db
@@ -49,6 +50,7 @@ class LogReader(BaseIOHandler):
4950
fetched_plugins = False
5051
message_readers = {
5152
".asc": ASCReader,
53+
".asc.gz": GzipASCReader,
5254
".blf": BLFReader,
5355
".csv": CSVReader,
5456
".db": SqliteReader,
@@ -75,7 +77,7 @@ def __new__( # type: ignore
7577
)
7678
LogReader.fetched_plugins = True
7779

78-
suffix = pathlib.PurePath(filename).suffix.lower()
80+
suffix = "".join(s.lower() for s in pathlib.PurePath(filename).suffixes)
7981
try:
8082
return typing.cast(
8183
MessageReader,

test/logformats_test.py

+29-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
1212
TODO: correctly set preserves_channel and adds_default_channel
1313
"""
14-
14+
import gzip
1515
import logging
1616
import unittest
1717
import tempfile
@@ -555,6 +555,34 @@ def test_can_and_canfd_error_frames(self):
555555
self.assertMessagesEqual(actual, expected_messages)
556556

557557

558+
class TestGzipASCFileFormat(ReaderWriterTest):
559+
"""Tests can.GzipASCWriter and can.GzipASCReader"""
560+
561+
def _setup_instance(self):
562+
super()._setup_instance_helper(
563+
can.GzipASCWriter,
564+
can.GzipASCReader,
565+
binary_file=True,
566+
check_comments=True,
567+
preserves_channel=False,
568+
adds_default_channel=0,
569+
)
570+
571+
def assertIncludesComments(self, filename):
572+
"""
573+
Ensures that all comments are literally contained in the given file.
574+
575+
:param filename: the path-like object to use
576+
"""
577+
if self.original_comments:
578+
# read the entire outout file
579+
with gzip.open(filename, "rt" if self.binary_file else "r") as file:
580+
output_contents = file.read()
581+
# check each, if they can be found in there literally
582+
for comment in self.original_comments:
583+
self.assertIn(comment, output_contents)
584+
585+
558586
class TestBlfFileFormat(ReaderWriterTest):
559587
"""Tests can.BLFWriter and can.BLFReader.
560588

0 commit comments

Comments
 (0)