vimgolf_gym.log_parser

Log parser for VimGolf executor

  1"""
  2Log parser for VimGolf executor
  3"""
  4
  5import copy
  6import json
  7import os
  8from abc import ABC, abstractmethod
  9from typing import Type
 10
 11from vimgolf_gym.dataclasses import VimGolfEnvResult
 12
 13
 14class AbstractLogParser(ABC):
 15    def __init__(self): ...
 16    @abstractmethod
 17    def feed_line(self, line: str): ...
 18
 19
 20# We keep track of the log file attributes.
 21# Specifically, the file size.
 22# If the file size changes, we will reparse the log.
 23# In more advanced usage, we could seek to given point and parse from there, avoiding reparsing from the beginning.
 24class LogWatcher:
 25    def __init__(self, log_file: str, parser_class: Type[AbstractLogParser]):
 26        """
 27        Initialize the log watcher with a log file and a parser class.
 28
 29        Args:
 30            log_file (str): The path to the log file.
 31            parser_class (Type[AbstractLogParser]): A class that implements the AbstractLogParser interface.
 32
 33        Attributes:
 34            log_file (str): The path to the log file.
 35            parser_class (Type[AbstractLogParser]): The class of the parser.
 36            parser (AbstractLogParser): An instance of the parser class.
 37            last_filesize (int): The size of the log file when it was last checked.
 38            last_position (int): The last read position in the log file.
 39        """
 40        self.log_file = log_file
 41        self.parser_class = parser_class
 42        self.parser = parser_class()
 43        self.last_filesize = 0
 44        self.last_position = 0  # Track the last read position
 45
 46    def update(self, style="advanced"):
 47        """
 48        Update the log watcher using one of three strategies.
 49
 50        The ``advanced`` strategy is the default. It checks the file size
 51        and only reads new content if the file size has changed. If the file
 52        size is smaller than the last recorded size, it resets the parser and
 53        reads the entire file.
 54
 55        The ``simple`` strategy reads the entire log file every time it is
 56        called. This is simple, but slow for large log files.
 57
 58        The ``naive`` strategy reads the entire log file every time it is
 59        called, but it does not reset the parser. This is faster than the
 60        ``simple`` strategy, but it does not handle the case where the log
 61        file is truncated.
 62
 63        Args:
 64            style (str, optional): The update strategy to use. Must be one of 
 65                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
 66        """
 67        if style == "advanced":
 68            self.advanced_update()
 69        elif style == "simple":
 70            self.simple_update()
 71        elif style == "naive":
 72            self.naive_update()
 73        else:
 74            raise ValueError(
 75                "Unrecognized update option: %s (should be in advanced, simple, naive)"
 76                % style
 77            )
 78
 79    def simple_update(self):
 80        """
 81        Read the entire log file and reset the parser if the file size has changed.
 82
 83        The ``simple`` strategy reads the entire log file every time it is
 84        called. This is simple, but slow for large log files.
 85        """
 86        current_size = (
 87            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
 88        )
 89        if current_size != self.last_filesize:
 90            self.naive_update()
 91            self.last_filesize = current_size
 92
 93    def naive_update(self):
 94        """
 95        Reset the parser and read the entire log file.
 96
 97        This is slow for large log files, but it is simple and handles
 98        the case where the log file is truncated.
 99
100        """
101        self.parser = self.parser_class()
102        if os.path.exists(self.log_file):
103            with open(self.log_file, "r") as f:
104                for line in f.readlines():
105                    self.parser.feed_line(line)
106        self.last_filesize = (
107            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
108        )
109        self.last_position = self.last_filesize
110
111    def advanced_update(self):
112        """
113        Read only the new content from the log file.
114
115        The ``advanced`` strategy reads only the new content from the log file,
116        starting from the last recorded position. This is fast for large log
117        files, but it requires keeping track of the last recorded position.
118
119        If the file size has decreased, it resets the parser and reads the
120        entire file.
121        """
122        if not os.path.exists(self.log_file):
123            return
124
125        current_size = os.path.getsize(self.log_file)
126
127        # If file size decreased (file was truncated), do a full reset
128        if current_size < self.last_filesize:
129            self.naive_update()
130            return
131
132        # If file size increased, read only the new content
133        if current_size > self.last_filesize:
134            with open(self.log_file, "r") as f:
135                f.seek(self.last_position)
136                while True:
137                    line = f.readline()
138                    if not line:  # End of file
139                        break
140                    self.parser.feed_line(line)
141
142                # Update tracking variables
143                self.last_filesize = current_size
144                self.last_position = f.tell()
145
146
147class VimGolfLogWatcher(LogWatcher):
148    def __init__(self, log_file: str, update_style="advanced"):
149        """
150        Initialize a VimGolfLogWatcher with a log file and an update style.
151
152        The VimGolfLogWatcher is a LogWatcher that is specialized for
153        reading VimGolf logs.
154
155        Args:
156            log_file (str): The path to the log file.
157            update_style (str, optional): The update strategy to use. Must be one of 
158                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
159
160        Returns:
161            [return_type]: [description of return value]
162        """
163        super().__init__(log_file=log_file, parser_class=VimGolfLogParser)
164        self.parser: VimGolfLogParser
165        self.update_style = update_style
166
167    def default_update(self):
168        """
169        Call the update method with the default update style.
170
171        This method is a convenience wrapper around the update method,
172        calling it with the default update style specified in the
173        constructor.
174
175        See the update method for more details.
176        """
177        self.update(style=self.update_style)
178
179    @property
180    def success(self):
181        """
182        Check if the vimgolf challenge has been solved successfully.
183
184        This property checks if the vimgolf challenge has been solved
185        successfully. It updates the log watcher if necessary before
186        checking the success status.
187
188        Returns:
189            bool: True if the challenge has been solved successfully, False otherwise.
190        """
191        self.default_update()
192        return self.parser.success
193
194    def get_best_success_result(self):
195        """
196        Return the best success result in the log watcher.
197
198        This method returns the best success result in the log watcher,
199        updating the log watcher if necessary before returning the result.
200
201        Returns:
202            VimGolfEnvResult: The best success result in the log watcher, or None if 
203            there is no success result.
204        """
205        self.default_update()
206        return self.parser.get_best_success_result()
207
208    def get_last_success_result(self):
209        """
210        Return the last success result in the log watcher.
211
212        This method returns the last success result in the log watcher,
213        updating the log watcher if necessary before returning the result.
214
215        Returns:
216            VimGolfEnvResult: The last success result in the log watcher, or None if 
217            there is no success result.
218        """
219        self.default_update()
220        return self.parser.get_last_success_result()
221
222    @property
223    def results(self):
224        """
225        The results of the vimgolf challenge environment.
226
227        This property returns the results of the vimgolf challenge environment,
228        updating the log watcher if necessary before returning the results.
229
230        Returns:
231            list[VimGolfEnvResult]: The results of the vimgolf challenge environment, or an empty list if
232            there are no results.
233        """
234        self.default_update()
235        return self.parser.results
236
237    @property
238    def success_results(self):
239        """
240        The successful results of the vimgolf challenge environment.
241
242        This property returns the successful results of the vimgolf challenge
243        environment, updating the log watcher if necessary before returning the
244        results.
245
246        Returns:
247            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
248            empty list if there are no successful results.
249        """
250        self.default_update()
251        return self.parser.success_results
252
253
254class VimGolfLogParser(AbstractLogParser):
255    def __init__(self):
256        """
257        Initialize the VimGolfLogParser.
258
259        The VimGolfLogParser is initialized with an empty list of results.
260        """
261        self.results: list[VimGolfEnvResult] = []
262
263    def feed_line(self, line: str):
264        """
265        Feed a line to the parser.
266
267        The line should be a JSON-formatted string. The parser will attempt to
268        parse the line as a JSON object, and if it is a dictionary, it will
269        check if the dictionary has an "event_type" key with value
270        "vimgolf_result". If so, it will attempt to parse the value of the
271        "event_data" key as a VimGolfEnvResult object and append it to the
272        results list.
273
274        If the line is not a valid JSON object, or if the JSON object does not
275        have the correct structure, the line will be ignored.
276
277        Args:
278            line (str): The line of text to feed to the parser.
279        """
280        try:
281            data = json.loads(line.strip())
282            if type(data) == dict:
283                if data.get("event_type", None) == "vimgolf_result":
284                    event_data = data.get("event_data", None)
285                    if type(event_data) == dict:
286                        parsed_result = VimGolfEnvResult.parse_obj(event_data)
287                        self.results.append(parsed_result)
288        except json.JSONDecodeError:
289            ...
290
291    @property
292    def success_results(self):
293        """
294        The successful results of the vimgolf challenge environment.
295
296        This property returns the successful results of the vimgolf challenge
297        environment, which are the results in the results list where the
298        correct attribute is True.
299
300        Returns:
301            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
302            empty list if there are no successful results.
303        """
304        return [it for it in self.results if it.correct]
305
306    @property
307    def success(self):
308        """
309        Check if the vimgolf challenge has been solved successfully.
310
311        This property checks if the vimgolf challenge has been solved
312        successfully. It returns True if there are any successful results in
313        the results list, and False otherwise.
314
315        Returns:
316            bool: True if the challenge has been solved successfully, False otherwise.
317        """
318        return len(self.success_results) != 0
319
320    def get_last_success_result(self):
321        """
322        Return the last success result in the log watcher.
323
324        This method returns the last success result in the log watcher,
325        which is the last result in the success_results list.
326
327        Returns:
328            VimGolfEnvResult: The last success result in the log watcher, or None if there is no
329            success result.
330        """
331        success_results = self.success_results
332        if success_results:
333            return success_results[-1]
334
335    def get_best_success_result(self):
336        """Return the result with the lowest score"""
337        success_results = copy.deepcopy(self.success_results)
338        if success_results:
339            success_results.sort(key=lambda x: x.score)
340            return success_results[0]
class AbstractLogParser(abc.ABC):
15class AbstractLogParser(ABC):
16    def __init__(self): ...
17    @abstractmethod
18    def feed_line(self, line: str): ...

Helper class that provides a standard way to create an ABC using inheritance.

@abstractmethod
def feed_line(self, line: str):
17    @abstractmethod
18    def feed_line(self, line: str): ...
class LogWatcher:
 25class LogWatcher:
 26    def __init__(self, log_file: str, parser_class: Type[AbstractLogParser]):
 27        """
 28        Initialize the log watcher with a log file and a parser class.
 29
 30        Args:
 31            log_file (str): The path to the log file.
 32            parser_class (Type[AbstractLogParser]): A class that implements the AbstractLogParser interface.
 33
 34        Attributes:
 35            log_file (str): The path to the log file.
 36            parser_class (Type[AbstractLogParser]): The class of the parser.
 37            parser (AbstractLogParser): An instance of the parser class.
 38            last_filesize (int): The size of the log file when it was last checked.
 39            last_position (int): The last read position in the log file.
 40        """
 41        self.log_file = log_file
 42        self.parser_class = parser_class
 43        self.parser = parser_class()
 44        self.last_filesize = 0
 45        self.last_position = 0  # Track the last read position
 46
 47    def update(self, style="advanced"):
 48        """
 49        Update the log watcher using one of three strategies.
 50
 51        The ``advanced`` strategy is the default. It checks the file size
 52        and only reads new content if the file size has changed. If the file
 53        size is smaller than the last recorded size, it resets the parser and
 54        reads the entire file.
 55
 56        The ``simple`` strategy reads the entire log file every time it is
 57        called. This is simple, but slow for large log files.
 58
 59        The ``naive`` strategy reads the entire log file every time it is
 60        called, but it does not reset the parser. This is faster than the
 61        ``simple`` strategy, but it does not handle the case where the log
 62        file is truncated.
 63
 64        Args:
 65            style (str, optional): The update strategy to use. Must be one of 
 66                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
 67        """
 68        if style == "advanced":
 69            self.advanced_update()
 70        elif style == "simple":
 71            self.simple_update()
 72        elif style == "naive":
 73            self.naive_update()
 74        else:
 75            raise ValueError(
 76                "Unrecognized update option: %s (should be in advanced, simple, naive)"
 77                % style
 78            )
 79
 80    def simple_update(self):
 81        """
 82        Read the entire log file and reset the parser if the file size has changed.
 83
 84        The ``simple`` strategy reads the entire log file every time it is
 85        called. This is simple, but slow for large log files.
 86        """
 87        current_size = (
 88            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
 89        )
 90        if current_size != self.last_filesize:
 91            self.naive_update()
 92            self.last_filesize = current_size
 93
 94    def naive_update(self):
 95        """
 96        Reset the parser and read the entire log file.
 97
 98        This is slow for large log files, but it is simple and handles
 99        the case where the log file is truncated.
100
101        """
102        self.parser = self.parser_class()
103        if os.path.exists(self.log_file):
104            with open(self.log_file, "r") as f:
105                for line in f.readlines():
106                    self.parser.feed_line(line)
107        self.last_filesize = (
108            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
109        )
110        self.last_position = self.last_filesize
111
112    def advanced_update(self):
113        """
114        Read only the new content from the log file.
115
116        The ``advanced`` strategy reads only the new content from the log file,
117        starting from the last recorded position. This is fast for large log
118        files, but it requires keeping track of the last recorded position.
119
120        If the file size has decreased, it resets the parser and reads the
121        entire file.
122        """
123        if not os.path.exists(self.log_file):
124            return
125
126        current_size = os.path.getsize(self.log_file)
127
128        # If file size decreased (file was truncated), do a full reset
129        if current_size < self.last_filesize:
130            self.naive_update()
131            return
132
133        # If file size increased, read only the new content
134        if current_size > self.last_filesize:
135            with open(self.log_file, "r") as f:
136                f.seek(self.last_position)
137                while True:
138                    line = f.readline()
139                    if not line:  # End of file
140                        break
141                    self.parser.feed_line(line)
142
143                # Update tracking variables
144                self.last_filesize = current_size
145                self.last_position = f.tell()
LogWatcher( log_file: str, parser_class: Type[AbstractLogParser])
26    def __init__(self, log_file: str, parser_class: Type[AbstractLogParser]):
27        """
28        Initialize the log watcher with a log file and a parser class.
29
30        Args:
31            log_file (str): The path to the log file.
32            parser_class (Type[AbstractLogParser]): A class that implements the AbstractLogParser interface.
33
34        Attributes:
35            log_file (str): The path to the log file.
36            parser_class (Type[AbstractLogParser]): The class of the parser.
37            parser (AbstractLogParser): An instance of the parser class.
38            last_filesize (int): The size of the log file when it was last checked.
39            last_position (int): The last read position in the log file.
40        """
41        self.log_file = log_file
42        self.parser_class = parser_class
43        self.parser = parser_class()
44        self.last_filesize = 0
45        self.last_position = 0  # Track the last read position

Initialize the log watcher with a log file and a parser class.

Arguments:
  • log_file (str): The path to the log file.
  • parser_class (Type[AbstractLogParser]): A class that implements the AbstractLogParser interface.
Attributes:
  • log_file (str): The path to the log file.
  • parser_class (Type[AbstractLogParser]): The class of the parser.
  • parser (AbstractLogParser): An instance of the parser class.
  • last_filesize (int): The size of the log file when it was last checked.
  • last_position (int): The last read position in the log file.
log_file
parser_class
parser
last_filesize
last_position
def update(self, style='advanced'):
47    def update(self, style="advanced"):
48        """
49        Update the log watcher using one of three strategies.
50
51        The ``advanced`` strategy is the default. It checks the file size
52        and only reads new content if the file size has changed. If the file
53        size is smaller than the last recorded size, it resets the parser and
54        reads the entire file.
55
56        The ``simple`` strategy reads the entire log file every time it is
57        called. This is simple, but slow for large log files.
58
59        The ``naive`` strategy reads the entire log file every time it is
60        called, but it does not reset the parser. This is faster than the
61        ``simple`` strategy, but it does not handle the case where the log
62        file is truncated.
63
64        Args:
65            style (str, optional): The update strategy to use. Must be one of 
66                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
67        """
68        if style == "advanced":
69            self.advanced_update()
70        elif style == "simple":
71            self.simple_update()
72        elif style == "naive":
73            self.naive_update()
74        else:
75            raise ValueError(
76                "Unrecognized update option: %s (should be in advanced, simple, naive)"
77                % style
78            )

Update the log watcher using one of three strategies.

The advanced strategy is the default. It checks the file size and only reads new content if the file size has changed. If the file size is smaller than the last recorded size, it resets the parser and reads the entire file.

The simple strategy reads the entire log file every time it is called. This is simple, but slow for large log files.

The naive strategy reads the entire log file every time it is called, but it does not reset the parser. This is faster than the simple strategy, but it does not handle the case where the log file is truncated.

Arguments:
  • style (str, optional): The update strategy to use. Must be one of advanced, simple, or naive. Defaults to advanced.
def simple_update(self):
80    def simple_update(self):
81        """
82        Read the entire log file and reset the parser if the file size has changed.
83
84        The ``simple`` strategy reads the entire log file every time it is
85        called. This is simple, but slow for large log files.
86        """
87        current_size = (
88            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
89        )
90        if current_size != self.last_filesize:
91            self.naive_update()
92            self.last_filesize = current_size

Read the entire log file and reset the parser if the file size has changed.

The simple strategy reads the entire log file every time it is called. This is simple, but slow for large log files.

def naive_update(self):
 94    def naive_update(self):
 95        """
 96        Reset the parser and read the entire log file.
 97
 98        This is slow for large log files, but it is simple and handles
 99        the case where the log file is truncated.
100
101        """
102        self.parser = self.parser_class()
103        if os.path.exists(self.log_file):
104            with open(self.log_file, "r") as f:
105                for line in f.readlines():
106                    self.parser.feed_line(line)
107        self.last_filesize = (
108            os.path.getsize(self.log_file) if os.path.exists(self.log_file) else 0
109        )
110        self.last_position = self.last_filesize

Reset the parser and read the entire log file.

This is slow for large log files, but it is simple and handles the case where the log file is truncated.

def advanced_update(self):
112    def advanced_update(self):
113        """
114        Read only the new content from the log file.
115
116        The ``advanced`` strategy reads only the new content from the log file,
117        starting from the last recorded position. This is fast for large log
118        files, but it requires keeping track of the last recorded position.
119
120        If the file size has decreased, it resets the parser and reads the
121        entire file.
122        """
123        if not os.path.exists(self.log_file):
124            return
125
126        current_size = os.path.getsize(self.log_file)
127
128        # If file size decreased (file was truncated), do a full reset
129        if current_size < self.last_filesize:
130            self.naive_update()
131            return
132
133        # If file size increased, read only the new content
134        if current_size > self.last_filesize:
135            with open(self.log_file, "r") as f:
136                f.seek(self.last_position)
137                while True:
138                    line = f.readline()
139                    if not line:  # End of file
140                        break
141                    self.parser.feed_line(line)
142
143                # Update tracking variables
144                self.last_filesize = current_size
145                self.last_position = f.tell()

Read only the new content from the log file.

The advanced strategy reads only the new content from the log file, starting from the last recorded position. This is fast for large log files, but it requires keeping track of the last recorded position.

If the file size has decreased, it resets the parser and reads the entire file.

class VimGolfLogWatcher(LogWatcher):
148class VimGolfLogWatcher(LogWatcher):
149    def __init__(self, log_file: str, update_style="advanced"):
150        """
151        Initialize a VimGolfLogWatcher with a log file and an update style.
152
153        The VimGolfLogWatcher is a LogWatcher that is specialized for
154        reading VimGolf logs.
155
156        Args:
157            log_file (str): The path to the log file.
158            update_style (str, optional): The update strategy to use. Must be one of 
159                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
160
161        Returns:
162            [return_type]: [description of return value]
163        """
164        super().__init__(log_file=log_file, parser_class=VimGolfLogParser)
165        self.parser: VimGolfLogParser
166        self.update_style = update_style
167
168    def default_update(self):
169        """
170        Call the update method with the default update style.
171
172        This method is a convenience wrapper around the update method,
173        calling it with the default update style specified in the
174        constructor.
175
176        See the update method for more details.
177        """
178        self.update(style=self.update_style)
179
180    @property
181    def success(self):
182        """
183        Check if the vimgolf challenge has been solved successfully.
184
185        This property checks if the vimgolf challenge has been solved
186        successfully. It updates the log watcher if necessary before
187        checking the success status.
188
189        Returns:
190            bool: True if the challenge has been solved successfully, False otherwise.
191        """
192        self.default_update()
193        return self.parser.success
194
195    def get_best_success_result(self):
196        """
197        Return the best success result in the log watcher.
198
199        This method returns the best success result in the log watcher,
200        updating the log watcher if necessary before returning the result.
201
202        Returns:
203            VimGolfEnvResult: The best success result in the log watcher, or None if 
204            there is no success result.
205        """
206        self.default_update()
207        return self.parser.get_best_success_result()
208
209    def get_last_success_result(self):
210        """
211        Return the last success result in the log watcher.
212
213        This method returns the last success result in the log watcher,
214        updating the log watcher if necessary before returning the result.
215
216        Returns:
217            VimGolfEnvResult: The last success result in the log watcher, or None if 
218            there is no success result.
219        """
220        self.default_update()
221        return self.parser.get_last_success_result()
222
223    @property
224    def results(self):
225        """
226        The results of the vimgolf challenge environment.
227
228        This property returns the results of the vimgolf challenge environment,
229        updating the log watcher if necessary before returning the results.
230
231        Returns:
232            list[VimGolfEnvResult]: The results of the vimgolf challenge environment, or an empty list if
233            there are no results.
234        """
235        self.default_update()
236        return self.parser.results
237
238    @property
239    def success_results(self):
240        """
241        The successful results of the vimgolf challenge environment.
242
243        This property returns the successful results of the vimgolf challenge
244        environment, updating the log watcher if necessary before returning the
245        results.
246
247        Returns:
248            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
249            empty list if there are no successful results.
250        """
251        self.default_update()
252        return self.parser.success_results
VimGolfLogWatcher(log_file: str, update_style='advanced')
149    def __init__(self, log_file: str, update_style="advanced"):
150        """
151        Initialize a VimGolfLogWatcher with a log file and an update style.
152
153        The VimGolfLogWatcher is a LogWatcher that is specialized for
154        reading VimGolf logs.
155
156        Args:
157            log_file (str): The path to the log file.
158            update_style (str, optional): The update strategy to use. Must be one of 
159                ``advanced``, ``simple``, or ``naive``. Defaults to ``advanced``.
160
161        Returns:
162            [return_type]: [description of return value]
163        """
164        super().__init__(log_file=log_file, parser_class=VimGolfLogParser)
165        self.parser: VimGolfLogParser
166        self.update_style = update_style

Initialize a VimGolfLogWatcher with a log file and an update style.

The VimGolfLogWatcher is a LogWatcher that is specialized for reading VimGolf logs.

Arguments:
  • log_file (str): The path to the log file.
  • update_style (str, optional): The update strategy to use. Must be one of advanced, simple, or naive. Defaults to advanced.
Returns:

[return_type]: [description of return value]

update_style
def default_update(self):
168    def default_update(self):
169        """
170        Call the update method with the default update style.
171
172        This method is a convenience wrapper around the update method,
173        calling it with the default update style specified in the
174        constructor.
175
176        See the update method for more details.
177        """
178        self.update(style=self.update_style)

Call the update method with the default update style.

This method is a convenience wrapper around the update method, calling it with the default update style specified in the constructor.

See the update method for more details.

success
180    @property
181    def success(self):
182        """
183        Check if the vimgolf challenge has been solved successfully.
184
185        This property checks if the vimgolf challenge has been solved
186        successfully. It updates the log watcher if necessary before
187        checking the success status.
188
189        Returns:
190            bool: True if the challenge has been solved successfully, False otherwise.
191        """
192        self.default_update()
193        return self.parser.success

Check if the vimgolf challenge has been solved successfully.

This property checks if the vimgolf challenge has been solved successfully. It updates the log watcher if necessary before checking the success status.

Returns:

bool: True if the challenge has been solved successfully, False otherwise.

def get_best_success_result(self):
195    def get_best_success_result(self):
196        """
197        Return the best success result in the log watcher.
198
199        This method returns the best success result in the log watcher,
200        updating the log watcher if necessary before returning the result.
201
202        Returns:
203            VimGolfEnvResult: The best success result in the log watcher, or None if 
204            there is no success result.
205        """
206        self.default_update()
207        return self.parser.get_best_success_result()

Return the best success result in the log watcher.

This method returns the best success result in the log watcher, updating the log watcher if necessary before returning the result.

Returns:

VimGolfEnvResult: The best success result in the log watcher, or None if there is no success result.

def get_last_success_result(self):
209    def get_last_success_result(self):
210        """
211        Return the last success result in the log watcher.
212
213        This method returns the last success result in the log watcher,
214        updating the log watcher if necessary before returning the result.
215
216        Returns:
217            VimGolfEnvResult: The last success result in the log watcher, or None if 
218            there is no success result.
219        """
220        self.default_update()
221        return self.parser.get_last_success_result()

Return the last success result in the log watcher.

This method returns the last success result in the log watcher, updating the log watcher if necessary before returning the result.

Returns:

VimGolfEnvResult: The last success result in the log watcher, or None if there is no success result.

results
223    @property
224    def results(self):
225        """
226        The results of the vimgolf challenge environment.
227
228        This property returns the results of the vimgolf challenge environment,
229        updating the log watcher if necessary before returning the results.
230
231        Returns:
232            list[VimGolfEnvResult]: The results of the vimgolf challenge environment, or an empty list if
233            there are no results.
234        """
235        self.default_update()
236        return self.parser.results

The results of the vimgolf challenge environment.

This property returns the results of the vimgolf challenge environment, updating the log watcher if necessary before returning the results.

Returns:

list[VimGolfEnvResult]: The results of the vimgolf challenge environment, or an empty list if there are no results.

success_results
238    @property
239    def success_results(self):
240        """
241        The successful results of the vimgolf challenge environment.
242
243        This property returns the successful results of the vimgolf challenge
244        environment, updating the log watcher if necessary before returning the
245        results.
246
247        Returns:
248            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
249            empty list if there are no successful results.
250        """
251        self.default_update()
252        return self.parser.success_results

The successful results of the vimgolf challenge environment.

This property returns the successful results of the vimgolf challenge environment, updating the log watcher if necessary before returning the results.

Returns:

list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an empty list if there are no successful results.

class VimGolfLogParser(AbstractLogParser):
255class VimGolfLogParser(AbstractLogParser):
256    def __init__(self):
257        """
258        Initialize the VimGolfLogParser.
259
260        The VimGolfLogParser is initialized with an empty list of results.
261        """
262        self.results: list[VimGolfEnvResult] = []
263
264    def feed_line(self, line: str):
265        """
266        Feed a line to the parser.
267
268        The line should be a JSON-formatted string. The parser will attempt to
269        parse the line as a JSON object, and if it is a dictionary, it will
270        check if the dictionary has an "event_type" key with value
271        "vimgolf_result". If so, it will attempt to parse the value of the
272        "event_data" key as a VimGolfEnvResult object and append it to the
273        results list.
274
275        If the line is not a valid JSON object, or if the JSON object does not
276        have the correct structure, the line will be ignored.
277
278        Args:
279            line (str): The line of text to feed to the parser.
280        """
281        try:
282            data = json.loads(line.strip())
283            if type(data) == dict:
284                if data.get("event_type", None) == "vimgolf_result":
285                    event_data = data.get("event_data", None)
286                    if type(event_data) == dict:
287                        parsed_result = VimGolfEnvResult.parse_obj(event_data)
288                        self.results.append(parsed_result)
289        except json.JSONDecodeError:
290            ...
291
292    @property
293    def success_results(self):
294        """
295        The successful results of the vimgolf challenge environment.
296
297        This property returns the successful results of the vimgolf challenge
298        environment, which are the results in the results list where the
299        correct attribute is True.
300
301        Returns:
302            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
303            empty list if there are no successful results.
304        """
305        return [it for it in self.results if it.correct]
306
307    @property
308    def success(self):
309        """
310        Check if the vimgolf challenge has been solved successfully.
311
312        This property checks if the vimgolf challenge has been solved
313        successfully. It returns True if there are any successful results in
314        the results list, and False otherwise.
315
316        Returns:
317            bool: True if the challenge has been solved successfully, False otherwise.
318        """
319        return len(self.success_results) != 0
320
321    def get_last_success_result(self):
322        """
323        Return the last success result in the log watcher.
324
325        This method returns the last success result in the log watcher,
326        which is the last result in the success_results list.
327
328        Returns:
329            VimGolfEnvResult: The last success result in the log watcher, or None if there is no
330            success result.
331        """
332        success_results = self.success_results
333        if success_results:
334            return success_results[-1]
335
336    def get_best_success_result(self):
337        """Return the result with the lowest score"""
338        success_results = copy.deepcopy(self.success_results)
339        if success_results:
340            success_results.sort(key=lambda x: x.score)
341            return success_results[0]

Helper class that provides a standard way to create an ABC using inheritance.

VimGolfLogParser()
256    def __init__(self):
257        """
258        Initialize the VimGolfLogParser.
259
260        The VimGolfLogParser is initialized with an empty list of results.
261        """
262        self.results: list[VimGolfEnvResult] = []

Initialize the VimGolfLogParser.

The VimGolfLogParser is initialized with an empty list of results.

def feed_line(self, line: str):
264    def feed_line(self, line: str):
265        """
266        Feed a line to the parser.
267
268        The line should be a JSON-formatted string. The parser will attempt to
269        parse the line as a JSON object, and if it is a dictionary, it will
270        check if the dictionary has an "event_type" key with value
271        "vimgolf_result". If so, it will attempt to parse the value of the
272        "event_data" key as a VimGolfEnvResult object and append it to the
273        results list.
274
275        If the line is not a valid JSON object, or if the JSON object does not
276        have the correct structure, the line will be ignored.
277
278        Args:
279            line (str): The line of text to feed to the parser.
280        """
281        try:
282            data = json.loads(line.strip())
283            if type(data) == dict:
284                if data.get("event_type", None) == "vimgolf_result":
285                    event_data = data.get("event_data", None)
286                    if type(event_data) == dict:
287                        parsed_result = VimGolfEnvResult.parse_obj(event_data)
288                        self.results.append(parsed_result)
289        except json.JSONDecodeError:
290            ...

Feed a line to the parser.

The line should be a JSON-formatted string. The parser will attempt to parse the line as a JSON object, and if it is a dictionary, it will check if the dictionary has an "event_type" key with value "vimgolf_result". If so, it will attempt to parse the value of the "event_data" key as a VimGolfEnvResult object and append it to the results list.

If the line is not a valid JSON object, or if the JSON object does not have the correct structure, the line will be ignored.

Arguments:
  • line (str): The line of text to feed to the parser.
success_results
292    @property
293    def success_results(self):
294        """
295        The successful results of the vimgolf challenge environment.
296
297        This property returns the successful results of the vimgolf challenge
298        environment, which are the results in the results list where the
299        correct attribute is True.
300
301        Returns:
302            list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an
303            empty list if there are no successful results.
304        """
305        return [it for it in self.results if it.correct]

The successful results of the vimgolf challenge environment.

This property returns the successful results of the vimgolf challenge environment, which are the results in the results list where the correct attribute is True.

Returns:

list[VimGolfEnvResult]: The successful results of the vimgolf challenge environment, or an empty list if there are no successful results.

success
307    @property
308    def success(self):
309        """
310        Check if the vimgolf challenge has been solved successfully.
311
312        This property checks if the vimgolf challenge has been solved
313        successfully. It returns True if there are any successful results in
314        the results list, and False otherwise.
315
316        Returns:
317            bool: True if the challenge has been solved successfully, False otherwise.
318        """
319        return len(self.success_results) != 0

Check if the vimgolf challenge has been solved successfully.

This property checks if the vimgolf challenge has been solved successfully. It returns True if there are any successful results in the results list, and False otherwise.

Returns:

bool: True if the challenge has been solved successfully, False otherwise.

def get_last_success_result(self):
321    def get_last_success_result(self):
322        """
323        Return the last success result in the log watcher.
324
325        This method returns the last success result in the log watcher,
326        which is the last result in the success_results list.
327
328        Returns:
329            VimGolfEnvResult: The last success result in the log watcher, or None if there is no
330            success result.
331        """
332        success_results = self.success_results
333        if success_results:
334            return success_results[-1]

Return the last success result in the log watcher.

This method returns the last success result in the log watcher, which is the last result in the success_results list.

Returns:

VimGolfEnvResult: The last success result in the log watcher, or None if there is no success result.

def get_best_success_result(self):
336    def get_best_success_result(self):
337        """Return the result with the lowest score"""
338        success_results = copy.deepcopy(self.success_results)
339        if success_results:
340            success_results.sort(key=lambda x: x.score)
341            return success_results[0]

Return the result with the lowest score