cr-proc 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,201 @@
1
+ """Display utilities for CLI output."""
2
+ import sys
3
+ from datetime import datetime
4
+ from typing import Any
5
+
6
+
7
+ def display_time_info(time_info: dict[str, Any] | None, is_combined: bool = False) -> None:
8
+ """
9
+ Display elapsed time and time limit information.
10
+
11
+ Parameters
12
+ ----------
13
+ time_info : dict[str, Any] | None
14
+ Time information from check_time_limit, or None if no time data
15
+ is_combined : bool
16
+ Whether this is combined time info from multiple files
17
+ """
18
+ if not time_info:
19
+ return
20
+
21
+ if is_combined:
22
+ file_count = time_info.get("file_count", 1)
23
+ print(f"\nCOMBINED TIME REPORT ({file_count} recordings):", file=sys.stderr)
24
+ print(f"Total elapsed editing time: {time_info['minutes_elapsed']} minutes", file=sys.stderr)
25
+ print(f"Overall time span: {time_info['overall_span_minutes']:.2f} minutes", file=sys.stderr)
26
+ else:
27
+ print(
28
+ f"Elapsed editing time: {time_info['minutes_elapsed']} minutes",
29
+ file=sys.stderr,
30
+ )
31
+
32
+ first_ts = datetime.fromisoformat(
33
+ time_info["first_timestamp"].replace("Z", "+00:00")
34
+ )
35
+ last_ts = datetime.fromisoformat(
36
+ time_info["last_timestamp"].replace("Z", "+00:00")
37
+ )
38
+ time_span = (last_ts - first_ts).total_seconds() / 60
39
+
40
+ print(f"Time span (first to last edit): {time_span:.2f} minutes", file=sys.stderr)
41
+
42
+ if time_info["exceeds_limit"]:
43
+ print("\nTime limit exceeded!", file=sys.stderr)
44
+ print(f" Limit: {time_info['time_limit_minutes']} minutes", file=sys.stderr)
45
+ if not is_combined:
46
+ print(f" First edit: {time_info['first_timestamp']}", file=sys.stderr)
47
+ print(f" Last edit: {time_info['last_timestamp']}", file=sys.stderr)
48
+
49
+
50
+ def display_suspicious_event(event: dict[str, Any], show_details: bool) -> None:
51
+ """
52
+ Display a single suspicious event.
53
+
54
+ Parameters
55
+ ----------
56
+ event : dict[str, Any]
57
+ Suspicious event data
58
+ show_details : bool
59
+ Whether to show detailed autocomplete events
60
+ """
61
+ reason = event.get("reason", "unknown")
62
+
63
+ # Handle aggregate auto-complete events
64
+ if event.get("event_index") == -1 and "detailed_events" in event:
65
+ event_count = event["event_count"]
66
+ total_chars = event["total_chars"]
67
+ print(
68
+ f" Aggregate: {event_count} auto-complete/small paste events "
69
+ f"({total_chars} total chars)",
70
+ file=sys.stderr,
71
+ )
72
+
73
+ if show_details:
74
+ print(" Detailed events:", file=sys.stderr)
75
+ for detail in event["detailed_events"]:
76
+ detail_idx = detail["event_index"]
77
+ detail_lines = detail["line_count"]
78
+ detail_chars = detail["char_count"]
79
+ detail_frag = detail["newFragment"]
80
+ print(
81
+ f" Event #{detail_idx}: {detail_lines} lines, "
82
+ f"{detail_chars} chars",
83
+ file=sys.stderr,
84
+ )
85
+ print(" ```", file=sys.stderr)
86
+ for line in detail_frag.split("\n"):
87
+ print(f" {line}", file=sys.stderr)
88
+ print(" ```", file=sys.stderr)
89
+
90
+ elif "event_indices" in event and reason == "rapid one-line pastes (AI indicator)":
91
+ # Rapid paste sequences (AI indicator) - show aggregate style
92
+ indices = event["event_indices"]
93
+ print(
94
+ f" AI Rapid Paste: Events #{indices[0]}-#{indices[-1]} "
95
+ f"({event['line_count']} lines, {event['char_count']} chars, "
96
+ f"{len(indices)} events in < 1 second)",
97
+ file=sys.stderr,
98
+ )
99
+
100
+ if show_details and "detailed_events" in event:
101
+ # Combine all detailed events into one block
102
+ combined_content = "".join(
103
+ detail["newFragment"] for detail in event["detailed_events"]
104
+ )
105
+ print(" Combined output:", file=sys.stderr)
106
+ print(" ```", file=sys.stderr)
107
+ for line in combined_content.split("\n"):
108
+ print(f" {line}", file=sys.stderr)
109
+ print(" ```", file=sys.stderr)
110
+
111
+ elif "event_indices" in event:
112
+ # Other multi-event clusters
113
+ indices = event.get("event_indices", [event["event_index"]])
114
+ print(
115
+ f" Events #{indices[0]}-#{indices[-1]} ({reason}): "
116
+ f"{event['line_count']} lines, {event['char_count']} chars",
117
+ file=sys.stderr,
118
+ )
119
+
120
+ else:
121
+ new_fragment = event["newFragment"].replace("\n", "\n ")
122
+ print(
123
+ f" Event #{event['event_index']} ({reason}): "
124
+ f"{event['line_count']} lines, {event['char_count']} chars - "
125
+ f"newFragment:\n ```\n {new_fragment}\n ```",
126
+ file=sys.stderr,
127
+ )
128
+
129
+
130
+ def display_suspicious_events(
131
+ suspicious_events: list[dict[str, Any]], show_details: bool
132
+ ) -> None:
133
+ """
134
+ Display all suspicious events or success message.
135
+
136
+ Parameters
137
+ ----------
138
+ suspicious_events : list[dict[str, Any]]
139
+ List of suspicious events detected
140
+ show_details : bool
141
+ Whether to show detailed autocomplete events
142
+ """
143
+ if suspicious_events:
144
+ print("\nSuspicious events detected:", file=sys.stderr)
145
+
146
+ # Sort events by their index for chronological display
147
+ def get_sort_key(event: dict[str, Any]) -> int | float:
148
+ if "event_indices" in event and event["event_indices"]:
149
+ return event["event_indices"][0]
150
+ if "detailed_events" in event and event["detailed_events"]:
151
+ return event["detailed_events"][0].get("event_index", float("inf"))
152
+ event_idx = event.get("event_index", -1)
153
+ return event_idx if event_idx >= 0 else float("inf")
154
+
155
+ sorted_events = sorted(suspicious_events, key=get_sort_key)
156
+
157
+ for event in sorted_events:
158
+ display_suspicious_event(event, show_details)
159
+ else:
160
+ print("Success! No suspicious events detected.", file=sys.stderr)
161
+
162
+
163
+ def display_template_diff(diff_text: str) -> None:
164
+ """
165
+ Display template diff output when verification fails.
166
+
167
+ Parameters
168
+ ----------
169
+ diff_text : str
170
+ Unified diff text between template and initial snapshot
171
+ """
172
+ if not diff_text:
173
+ return
174
+
175
+ print("\nTemplate mismatch diff:", file=sys.stderr)
176
+ print(diff_text, file=sys.stderr)
177
+
178
+
179
+ def print_separator() -> None:
180
+ """Print a separator line."""
181
+ print(f"{'='*80}", file=sys.stderr)
182
+
183
+
184
+ def print_batch_header(current: int, total: int, filename: str) -> None:
185
+ """Print a batch processing header for a file."""
186
+ print_separator()
187
+ print(f"[{current}/{total}] Processing: {filename}", file=sys.stderr)
188
+ print_separator()
189
+
190
+
191
+ def print_batch_summary(total: int, verified_count: int, failed_files: list[str]) -> None:
192
+ """Print a summary of batch processing results."""
193
+ print_separator()
194
+ print(f"BATCH SUMMARY: Processed {total} files", file=sys.stderr)
195
+ print_separator()
196
+ print(f"Verified: {verified_count}/{total}", file=sys.stderr)
197
+
198
+ if failed_files:
199
+ print("\nFailed files:", file=sys.stderr)
200
+ for filename in failed_files:
201
+ print(f" - {filename}", file=sys.stderr)
@@ -0,0 +1,116 @@
1
+ """Playback functionality for viewing code evolution."""
2
+ import os
3
+ import sys
4
+ import time
5
+ from datetime import datetime
6
+ from typing import Any
7
+
8
+
9
+ def playback_recording(
10
+ json_data: tuple[dict[str, Any], ...],
11
+ document: str,
12
+ template: str,
13
+ speed: float = 1.0,
14
+ ) -> None:
15
+ """
16
+ Play back a recording, showing the code evolving in real-time.
17
+
18
+ Only plays back edit events (type="edit" or no type field for backwards compatibility).
19
+
20
+ Parameters
21
+ ----------
22
+ json_data : tuple[dict[str, Any], ...]
23
+ The recording events (all event types)
24
+ document : str
25
+ The document to play back
26
+ template : str
27
+ The initial template content
28
+ speed : float
29
+ Playback speed multiplier (1.0 = real-time, 2.0 = 2x speed, 0.5 = half speed)
30
+ """
31
+ # Filter to only edit events (backwards compatible)
32
+ from .api.load import is_edit_event
33
+ edit_events = [e for e in json_data if is_edit_event(e)]
34
+
35
+ # Filter events for the target document
36
+ doc_events = [e for e in edit_events if e.get("document") == document]
37
+
38
+ if not doc_events:
39
+ print(f"No events found for document: {document}", file=sys.stderr)
40
+ return
41
+
42
+ # Start with template
43
+ current_content = template
44
+ last_timestamp = None
45
+
46
+ def clear_screen():
47
+ """Clear the terminal screen."""
48
+ os.system('cls' if os.name == 'nt' else 'clear')
49
+
50
+ def parse_timestamp(ts_str: str) -> datetime:
51
+ """Parse ISO timestamp string."""
52
+ return datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
53
+
54
+ # Show initial template
55
+ clear_screen()
56
+ print(f"=" * 80)
57
+ print(f"PLAYBACK: {document} (Speed: {speed}x)")
58
+ print(f"Event 0 / {len(doc_events)} - Initial Template")
59
+ print(f"=" * 80)
60
+ print(current_content)
61
+ print(f"\n{'=' * 80}")
62
+ print("Press Ctrl+C to stop playback")
63
+ time.sleep(2.0 / speed)
64
+
65
+ try:
66
+ for idx, event in enumerate(doc_events, 1):
67
+ old_frag = event.get("oldFragment", "")
68
+ new_frag = event.get("newFragment", "")
69
+ offset = event.get("offset", 0)
70
+ timestamp = event.get("timestamp")
71
+
72
+ # Calculate delay based on timestamp difference
73
+ if last_timestamp and timestamp:
74
+ try:
75
+ ts1 = parse_timestamp(last_timestamp)
76
+ ts2 = parse_timestamp(timestamp)
77
+ delay = (ts2 - ts1).total_seconds() / speed
78
+ # Cap delay at 5 seconds for very long pauses
79
+ delay = min(delay, 5.0)
80
+ if delay > 0:
81
+ time.sleep(delay)
82
+ except (ValueError, KeyError):
83
+ time.sleep(0.1 / speed)
84
+ else:
85
+ time.sleep(0.1 / speed)
86
+
87
+ last_timestamp = timestamp
88
+
89
+ # Apply the edit
90
+ if new_frag != old_frag:
91
+ current_content = current_content[:offset] + new_frag + current_content[offset + len(old_frag):]
92
+
93
+ # Display current state
94
+ clear_screen()
95
+ print(f"=" * 80)
96
+ print(f"PLAYBACK: {document} (Speed: {speed}x)")
97
+ print(f"Event {idx} / {len(doc_events)} - {timestamp or 'unknown time'}")
98
+
99
+ # Show what changed
100
+ if new_frag != old_frag:
101
+ change_type = "INSERT" if not old_frag else ("DELETE" if not new_frag else "REPLACE")
102
+ print(f"Action: {change_type} at offset {offset} ({len(new_frag)} chars)")
103
+
104
+ print(f"=" * 80)
105
+ print(current_content)
106
+ print(f"\n{'=' * 80}")
107
+ print(f"Progress: [{('#' * (idx * 40 // len(doc_events))).ljust(40)}] {idx}/{len(doc_events)}")
108
+ print("Press Ctrl+C to stop playback")
109
+
110
+ except KeyboardInterrupt:
111
+ print("\n\nPlayback stopped by user.", file=sys.stderr)
112
+ return
113
+
114
+ # Final summary
115
+ print("\n\nPlayback complete!", file=sys.stderr)
116
+ print(f"Total events: {len(doc_events)}", file=sys.stderr)
@@ -0,0 +1,280 @@
1
+ Metadata-Version: 2.4
2
+ Name: cr_proc
3
+ Version: 0.1.9
4
+ Summary: A tool for processing BYU CS code recording files.
5
+ Author: Ethan Dye
6
+ Author-email: mrtops03@gmail.com
7
+ Requires-Python: >=3.14
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.14
10
+ Requires-Dist: py-jsonl (>=1.3.22,<2.0.0)
11
+ Description-Content-Type: text/markdown
12
+
13
+ # `code_recorder_processor`
14
+
15
+ [![CI](https://github.com/BYU-CS-Course-Ops/code_recorder_processor/actions/workflows/ci.yml/badge.svg)](https://github.com/BYU-CS-Course-Ops/code_recorder_processor/actions/workflows/ci.yml)
16
+
17
+ This contains code to process and verify the `*.recorder.jsonl.gz` files that
18
+ are produced by the
19
+ [jetbrains-recorder](https://github.com/BYU-CS-Course-Ops/jetbrains-recorder).
20
+
21
+ ## Installation
22
+
23
+ Install the package and its dependencies using Poetry:
24
+
25
+ ```bash
26
+ poetry install
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ The processor can be run using the `cr_proc` command with recording file(s) and a template:
32
+
33
+ ```bash
34
+ poetry run cr_proc <path-to-jsonl-file> <path-to-template-file>
35
+ ```
36
+
37
+ ### Batch Processing
38
+
39
+ You can process multiple recording files at once (e.g., for different students' submissions):
40
+
41
+ ```bash
42
+ # Process multiple files
43
+ poetry run cr_proc file1.jsonl.gz file2.jsonl.gz template.py
44
+
45
+ # Using glob patterns
46
+ poetry run cr_proc recordings/*.jsonl.gz template.py
47
+ ```
48
+
49
+ When processing multiple files:
50
+ - Each recording is processed independently (for different students/documents)
51
+ - Time calculations and verification are done separately for each file
52
+ - A combined time report is shown at the end summarizing total editing time across all recordings
53
+ - Results can be output to individual files using `--output-dir`
54
+
55
+ ### Arguments
56
+
57
+ - `<path-to-jsonl-file>`: Path(s) to compressed JSONL file(s)
58
+ (`*.recorder.jsonl.gz`) produced by the jetbrains-recorder. Supports multiple
59
+ files and glob patterns like `recordings/*.jsonl.gz`
60
+ - `<path-to-template-file>`: Path to the initial template file that was recorded
61
+
62
+ ### Options
63
+
64
+ - `-t, --time-limit MINUTES`: (Optional) Maximum allowed time in minutes between the
65
+ first and last edit in the recording. Applied individually to each recording file and
66
+ also to the combined total in batch mode. If the elapsed time exceeds this limit, the
67
+ recording is flagged as suspicious.
68
+ - `-d, --document DOCUMENT`: (Optional) Document path or filename to process from the
69
+ recording. Defaults to the document whose extension matches the template file.
70
+ - `-o, --output-json OUTPUT_JSON`: (Optional) Path to output JSON file with verification
71
+ results (time info and suspicious events). In batch mode, creates a single JSON file
72
+ containing all recordings plus the combined time report.
73
+ - `-f, --output-file OUTPUT_FILE`: (Optional) Write reconstructed code to specified file
74
+ instead of stdout. For single files only.
75
+ - `--output-dir OUTPUT_DIR`: (Optional) Directory to write reconstructed code files in
76
+ batch mode. Files are named based on input recording filenames.
77
+ - `-s, --show-autocomplete-details`: (Optional) Show individual auto-complete events in
78
+ addition to aggregate statistics.
79
+ - `-p, --playback`: (Optional) Play back the recording in real-time, showing code evolution.
80
+ - `--playback-speed SPEED`: (Optional) Playback speed multiplier (1.0 = real-time, 2.0 = 2x
81
+ speed, 0.5 = half speed).
82
+
83
+ ### Examples
84
+
85
+ Basic usage:
86
+
87
+ ```bash
88
+ poetry run cr_proc homework0.recording.jsonl.gz homework0.py
89
+ ```
90
+
91
+ With time limit flag:
92
+
93
+ ```bash
94
+ poetry run cr_proc homework0.recording.jsonl.gz homework0.py --time-limit 30
95
+ ```
96
+
97
+ Batch processing with output directory:
98
+
99
+ ```bash
100
+ poetry run cr_proc recordings/*.jsonl.gz template.py --output-dir output/
101
+ ```
102
+
103
+ Save JSON results:
104
+
105
+ ```bash
106
+ poetry run cr_proc student1.jsonl.gz student2.jsonl.gz template.py -o results/
107
+ ```
108
+
109
+ This will process each recording independently and flag any that exceed 30 minutes.
110
+
111
+ The processor will:
112
+
113
+ 1. Load the recorded events from the JSONL file
114
+ 2. Verify that the initial event matches the template (allowances for newline
115
+ differences are made)
116
+ 3. Reconstruct the final file state by applying all recorded events
117
+ 4. Output the reconstructed file contents to stdout
118
+
119
+ ### Output
120
+
121
+ Reconstructed code files are written to disk using `-f/--output-file` (single file)
122
+ or `--output-dir` (batch mode). The processor does not output reconstructed code to stdout.
123
+
124
+ Verification information, warnings, and errors are printed to stderr, including:
125
+
126
+ - The document path being processed
127
+ - Time information (elapsed time, time span) for each recording
128
+ - Suspicious copy-paste and AI activity indicators for each file
129
+ - Batch summary showing:
130
+ - Verification status of all processed files
131
+ - Combined time report (total editing time across all recordings)
132
+ - Time limit violations if applicable
133
+
134
+ ### Suspicious Activity Detection
135
+
136
+ The processor automatically detects and reports three types of suspicious activity
137
+ patterns:
138
+
139
+ #### 1. Time Limit Exceeded
140
+
141
+ When the `--time-limit` flag is specified, the processor flags recordings where
142
+ the elapsed time between the first and last edit exceeds the specified limit.
143
+ This can indicate unusually long work sessions or potential external assistance.
144
+
145
+ Each recording file is checked independently against the time limit. In batch mode,
146
+ the combined total time is also checked against the limit.
147
+
148
+ **Example warning (single file):**
149
+
150
+ ```
151
+ Elapsed editing time: 45.5 minutes
152
+ Time span (first to last edit): 62.30 minutes
153
+
154
+ Time limit exceeded!
155
+ Limit: 30 minutes
156
+ First edit: 2025-01-15T10:00:00+00:00
157
+ Last edit: 2025-01-15T11:02:18+00:00
158
+ ```
159
+
160
+ **Example warning (batch mode combined report):**
161
+
162
+ ```
163
+ ================================================================================
164
+ BATCH SUMMARY: Processed 3 files
165
+ ================================================================================
166
+ Verified: 3/3
167
+
168
+ COMBINED TIME REPORT (3 recordings):
169
+ Total elapsed editing time: 65.5 minutes
170
+ Overall time span: 120.45 minutes
171
+
172
+ Time limit exceeded!
173
+ Limit: 60 minutes
174
+ ```
175
+
176
+ #### 2. External Copy-Paste (Multi-line Pastes)
177
+
178
+ The processor flags multi-line additions (more than one line) that do not appear
179
+ to be copied from within the document itself. These indicate content pasted from
180
+ external sources.
181
+
182
+ **Example warning:**
183
+
184
+ ```
185
+ Event #15 (multi-line external paste): 5 lines, 156 chars - newFragment: def helper_function():...
186
+ ```
187
+
188
+ #### 3. Rapid One-line Pastes (AI Indicator)
189
+
190
+ When 3 or more single-line pastes occur within a 1-second window, this is
191
+ flagged as a potential AI activity indicator. Human typing does not typically
192
+ produce this pattern; rapid sequential pastes suggest automated code generation.
193
+
194
+ **Example warning:**
195
+
196
+ ```
197
+ Events #42-#44 (rapid one-line pastes (AI indicator)): 3 lines, 89 chars
198
+ ```
199
+
200
+ ### JSON Output Format
201
+
202
+ The `--output-json` flag generates JSON files with verification results using a consistent format
203
+ for both single file and batch modes, making it easier for tooling to consume.
204
+
205
+ #### JSON Structure
206
+
207
+ All JSON output follows this unified format:
208
+ - `batch_mode`: Boolean indicating if multiple files were processed
209
+ - `total_files`: Number of files processed
210
+ - `verified_count`: How many files passed verification
211
+ - `all_verified`: Whether all files passed
212
+ - `combined_time_info`: Time information (present in both modes):
213
+ - Single file: Contains time info for that file
214
+ - Batch mode: Contains combined time report with:
215
+ - `minutes_elapsed`: Total editing time across all recordings
216
+ - `overall_span_minutes`: Time span from first to last edit
217
+ - `file_count`: Number of recordings
218
+ - `exceeds_limit`: Whether combined time exceeds the limit
219
+ - `files`: Array of individual results for each recording
220
+
221
+ **Single file example:**
222
+ ```json
223
+ {
224
+ "batch_mode": false,
225
+ "total_files": 1,
226
+ "verified_count": 1,
227
+ "all_verified": true,
228
+ "combined_time_info": {
229
+ "minutes_elapsed": 15.74,
230
+ "first_timestamp": "2026-01-15T01:21:35.360168Z",
231
+ "exceeds_limit": false
232
+ },
233
+ "files": [
234
+ {
235
+ "jsonl_file": "recording.jsonl.gz",
236
+ "document": "/path/to/homework.py",
237
+ "verified": true,
238
+ "time_info": { ... },
239
+ "suspicious_events": [ ... ],
240
+ "reconstructed_code": "..."
241
+ }
242
+ ]
243
+ }
244
+ ```
245
+
246
+ **Batch file example:**
247
+ ```json
248
+ {
249
+ "batch_mode": true,
250
+ "total_files": 2,
251
+ "verified_count": 2,
252
+ "all_verified": true,
253
+ "combined_time_info": {
254
+ "minutes_elapsed": 31.24,
255
+ "overall_span_minutes": 18739.29,
256
+ "file_count": 2,
257
+ "exceeds_limit": false
258
+ },
259
+ "files": [ /* individual results for each file */ ]
260
+ }
261
+ ```
262
+
263
+ ### Error Handling
264
+
265
+ If verification fails (the recorded initial state doesn't match the template),
266
+ the processor will:
267
+
268
+ - Print an error message to stderr
269
+ - Display a diff showing the differences
270
+ - Exit with status code 1
271
+
272
+ If file loading or processing errors occur, the processor will:
273
+
274
+ - Print a descriptive error message to stderr
275
+ - Exit with status code 1
276
+
277
+ ## Future Ideas
278
+
279
+ - Check for odd typing behavior
280
+
@@ -0,0 +1,13 @@
1
+ code_recorder_processor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ code_recorder_processor/api/build.py,sha256=tljtuEFH-ZU-hSFYmlAMSY61W-DSptQo_D5-GjAasco,7951
3
+ code_recorder_processor/api/document.py,sha256=mBvATBZs8yyCY_nDOX2qhw0Gp1mmwI3PgOAzFgHUiSY,9486
4
+ code_recorder_processor/api/load.py,sha256=Br-USpFQJ6W8c5hjmCnunM3V0_MURKZp5Yyl1IJdahc,5514
5
+ code_recorder_processor/api/output.py,sha256=H2SC3pQ0C9V8YyN4yeA_KmvSoWXy_3T3TKWKhywIax4,2161
6
+ code_recorder_processor/api/verify.py,sha256=9GpeoFQIiTzZd-DNSyN5OUM6YB5iMslO85oAjc0yoSU,34073
7
+ code_recorder_processor/cli.py,sha256=ardcM3bLNhf6abOQ1Aj746x4hp8gerdklfDwszLlYKc,20504
8
+ code_recorder_processor/display.py,sha256=IVTNFB3Vjzpc5ZHceAFQI2-o-N6bvjYmotLDaEy0KoU,7368
9
+ code_recorder_processor/playback.py,sha256=6-OJtQOHKgfutxUNBMunWl-VVSIB0zUDENSl0EsPCh4,4008
10
+ cr_proc-0.1.9.dist-info/METADATA,sha256=3yqgqvpe1juNoinP6Xn59UiowZen06mgFTh1eG2ZC8M,8915
11
+ cr_proc-0.1.9.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
12
+ cr_proc-0.1.9.dist-info/entry_points.txt,sha256=xb5dPAAWN1Z9NUHpvZgNakaslR1MVOERf_IfpG_M04M,77
13
+ cr_proc-0.1.9.dist-info/RECORD,,