scitex 2.4.2__py3-none-any.whl → 2.5.0__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.
Files changed (64) hide show
  1. scitex/__version__.py +1 -1
  2. scitex/browser/__init__.py +53 -0
  3. scitex/browser/debugging/__init__.py +56 -0
  4. scitex/browser/debugging/_failure_capture.py +372 -0
  5. scitex/browser/debugging/_sync_session.py +259 -0
  6. scitex/browser/debugging/_test_monitor.py +284 -0
  7. scitex/browser/debugging/_visual_cursor.py +432 -0
  8. scitex/io/_load.py +5 -0
  9. scitex/io/_load_modules/_canvas.py +171 -0
  10. scitex/io/_save.py +8 -0
  11. scitex/io/_save_modules/_canvas.py +356 -0
  12. scitex/plt/_subplots/_export_as_csv_formatters/_format_plot.py +77 -22
  13. scitex/plt/docs/FIGURE_ARCHITECTURE.md +257 -0
  14. scitex/plt/utils/__init__.py +10 -0
  15. scitex/plt/utils/_collect_figure_metadata.py +14 -12
  16. scitex/plt/utils/_csv_column_naming.py +237 -0
  17. scitex/scholar/citation_graph/database.py +9 -2
  18. scitex/scholar/config/ScholarConfig.py +23 -3
  19. scitex/scholar/config/default.yaml +55 -0
  20. scitex/scholar/core/Paper.py +102 -0
  21. scitex/scholar/core/__init__.py +44 -0
  22. scitex/scholar/core/journal_normalizer.py +524 -0
  23. scitex/scholar/core/oa_cache.py +285 -0
  24. scitex/scholar/core/open_access.py +457 -0
  25. scitex/scholar/pdf_download/ScholarPDFDownloader.py +137 -0
  26. scitex/scholar/pdf_download/strategies/__init__.py +6 -0
  27. scitex/scholar/pdf_download/strategies/open_access_download.py +186 -0
  28. scitex/scholar/pipelines/ScholarPipelineSearchParallel.py +18 -3
  29. scitex/scholar/pipelines/ScholarPipelineSearchSingle.py +15 -2
  30. scitex/session/_decorator.py +13 -1
  31. scitex/vis/README.md +246 -615
  32. scitex/vis/__init__.py +138 -78
  33. scitex/vis/canvas.py +423 -0
  34. scitex/vis/docs/CANVAS_ARCHITECTURE.md +307 -0
  35. scitex/vis/editor/__init__.py +1 -1
  36. scitex/vis/editor/_dearpygui_editor.py +1830 -0
  37. scitex/vis/editor/_defaults.py +40 -1
  38. scitex/vis/editor/_edit.py +54 -18
  39. scitex/vis/editor/_flask_editor.py +37 -0
  40. scitex/vis/editor/_qt_editor.py +865 -0
  41. scitex/vis/editor/flask_editor/__init__.py +21 -0
  42. scitex/vis/editor/flask_editor/bbox.py +216 -0
  43. scitex/vis/editor/flask_editor/core.py +152 -0
  44. scitex/vis/editor/flask_editor/plotter.py +130 -0
  45. scitex/vis/editor/flask_editor/renderer.py +184 -0
  46. scitex/vis/editor/flask_editor/templates/__init__.py +33 -0
  47. scitex/vis/editor/flask_editor/templates/html.py +295 -0
  48. scitex/vis/editor/flask_editor/templates/scripts.py +614 -0
  49. scitex/vis/editor/flask_editor/templates/styles.py +549 -0
  50. scitex/vis/editor/flask_editor/utils.py +81 -0
  51. scitex/vis/io/__init__.py +84 -21
  52. scitex/vis/io/canvas.py +226 -0
  53. scitex/vis/io/data.py +204 -0
  54. scitex/vis/io/directory.py +202 -0
  55. scitex/vis/io/export.py +460 -0
  56. scitex/vis/io/panel.py +424 -0
  57. {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/METADATA +9 -2
  58. {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/RECORD +61 -32
  59. scitex/vis/DJANGO_INTEGRATION.md +0 -677
  60. scitex/vis/editor/_web_editor.py +0 -1440
  61. scitex/vis/tmp.txt +0 -239
  62. {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/WHEEL +0 -0
  63. {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/entry_points.txt +0 -0
  64. {scitex-2.4.2.dist-info → scitex-2.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,549 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: ./src/scitex/vis/editor/flask_editor/templates/styles.py
4
+ """CSS styles for the Flask editor UI."""
5
+
6
+ CSS_STYLES = '''
7
+ /* =============================================================================
8
+ * SciTeX Color System - Based on scitex-cloud/static/shared/css
9
+ * ============================================================================= */
10
+ :root, [data-theme="light"] {
11
+ /* Brand colors (light mode) */
12
+ --scitex-01: #1a2a40;
13
+ --scitex-02: #34495e;
14
+ --scitex-03: #506b7a;
15
+ --scitex-04: #6c8ba0;
16
+ --scitex-05: #8fa4b0;
17
+ --scitex-06: #b5c7d1;
18
+ --scitex-07: #d4e1e8;
19
+ --white: #fafbfc;
20
+ --gray-subtle: #f6f8fa;
21
+
22
+ /* Semantic tokens */
23
+ --text-primary: var(--scitex-01);
24
+ --text-secondary: var(--scitex-02);
25
+ --text-muted: var(--scitex-04);
26
+ --text-inverse: var(--white);
27
+
28
+ --bg-page: #fefefe;
29
+ --bg-surface: var(--white);
30
+ --bg-muted: var(--gray-subtle);
31
+
32
+ --border-default: var(--scitex-05);
33
+ --border-muted: var(--scitex-06);
34
+
35
+ /* Workspace colors */
36
+ --workspace-bg-primary: #f8f9fa;
37
+ --workspace-bg-secondary: #f3f4f6;
38
+ --workspace-bg-tertiary: #ebedef;
39
+ --workspace-bg-elevated: #ffffff;
40
+ --workspace-border-subtle: #e0e4e8;
41
+ --workspace-border-default: #b5c7d1;
42
+
43
+ /* Status */
44
+ --status-success: #4a9b7e;
45
+ --status-warning: #b8956a;
46
+ --status-error: #a67373;
47
+
48
+ /* CTA */
49
+ --color-cta: #3b82f6;
50
+ --color-cta-hover: #2563eb;
51
+
52
+ /* Preview background (checkered for transparency) */
53
+ --preview-bg: linear-gradient(45deg, #e0e0e0 25%, transparent 25%),
54
+ linear-gradient(-45deg, #e0e0e0 25%, transparent 25%),
55
+ linear-gradient(45deg, transparent 75%, #e0e0e0 75%),
56
+ linear-gradient(-45deg, transparent 75%, #e0e0e0 75%);
57
+ }
58
+
59
+ [data-theme="dark"] {
60
+ /* Semantic tokens (dark mode) */
61
+ --text-primary: var(--scitex-07);
62
+ --text-secondary: var(--scitex-05);
63
+ --text-muted: var(--scitex-04);
64
+ --text-inverse: var(--scitex-01);
65
+
66
+ --bg-page: #0f1419;
67
+ --bg-surface: var(--scitex-01);
68
+ --bg-muted: var(--scitex-02);
69
+
70
+ --border-default: var(--scitex-03);
71
+ --border-muted: var(--scitex-02);
72
+
73
+ /* Workspace colors */
74
+ --workspace-bg-primary: #0d0d0d;
75
+ --workspace-bg-secondary: #151515;
76
+ --workspace-bg-tertiary: #1a1a1a;
77
+ --workspace-bg-elevated: #1f1f1f;
78
+ --workspace-border-subtle: #1a1a1a;
79
+ --workspace-border-default: #3a3a3a;
80
+
81
+ /* Status */
82
+ --status-success: #6ba89a;
83
+ --status-warning: #d4a87a;
84
+ --status-error: #c08888;
85
+
86
+ /* Preview background (darker checkered) */
87
+ --preview-bg: linear-gradient(45deg, #2a2a2a 25%, transparent 25%),
88
+ linear-gradient(-45deg, #2a2a2a 25%, transparent 25%),
89
+ linear-gradient(45deg, transparent 75%, #2a2a2a 75%),
90
+ linear-gradient(-45deg, transparent 75%, #2a2a2a 75%);
91
+ }
92
+
93
+ /* =============================================================================
94
+ * Base Styles
95
+ * ============================================================================= */
96
+ * { box-sizing: border-box; margin: 0; padding: 0; }
97
+
98
+ body {
99
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
100
+ background: var(--workspace-bg-primary);
101
+ color: var(--text-primary);
102
+ transition: background 0.3s, color 0.3s;
103
+ }
104
+
105
+ .container { display: flex; height: 100vh; }
106
+
107
+ /* =============================================================================
108
+ * Preview Panel
109
+ * ============================================================================= */
110
+ .preview {
111
+ flex: 2;
112
+ padding: 20px;
113
+ display: flex;
114
+ align-items: center;
115
+ justify-content: center;
116
+ background: var(--workspace-bg-secondary);
117
+ }
118
+
119
+ .preview-wrapper {
120
+ background: var(--preview-bg);
121
+ background-size: 20px 20px;
122
+ background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
123
+ border-radius: 8px;
124
+ padding: 10px;
125
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
126
+ }
127
+
128
+ .preview img {
129
+ max-width: 100%;
130
+ max-height: calc(100vh - 80px);
131
+ display: block;
132
+ }
133
+
134
+ /* Hover overlay for interactive selection */
135
+ .preview-container {
136
+ position: relative;
137
+ display: inline-block;
138
+ }
139
+
140
+ .hover-overlay {
141
+ position: absolute;
142
+ top: 0;
143
+ left: 0;
144
+ pointer-events: none;
145
+ }
146
+
147
+ .hover-rect {
148
+ fill: none;
149
+ stroke: rgba(100, 180, 255, 0.6);
150
+ stroke-width: 1;
151
+ pointer-events: none;
152
+ }
153
+
154
+ .selected-rect {
155
+ fill: none;
156
+ stroke: rgba(255, 200, 80, 0.8);
157
+ stroke-width: 2;
158
+ pointer-events: none;
159
+ }
160
+
161
+ .hover-label {
162
+ font-size: 10px;
163
+ fill: rgba(100, 180, 255, 0.9);
164
+ pointer-events: none;
165
+ }
166
+
167
+ .selected-label {
168
+ font-size: 10px;
169
+ fill: rgba(255, 200, 80, 0.9);
170
+ pointer-events: none;
171
+ }
172
+
173
+ .hover-path {
174
+ fill: none;
175
+ stroke: rgba(100, 180, 255, 0.9);
176
+ stroke-width: 4;
177
+ stroke-linecap: round;
178
+ stroke-linejoin: round;
179
+ pointer-events: none;
180
+ }
181
+
182
+ .selected-path {
183
+ fill: none;
184
+ stroke: rgba(255, 200, 80, 0.9);
185
+ stroke-width: 5;
186
+ stroke-linecap: round;
187
+ stroke-linejoin: round;
188
+ pointer-events: none;
189
+ }
190
+
191
+ /* =============================================================================
192
+ * Controls Panel
193
+ * ============================================================================= */
194
+ .controls {
195
+ flex: 1;
196
+ min-width: 320px;
197
+ max-width: 420px;
198
+ background: var(--workspace-bg-elevated);
199
+ border-left: 1px solid var(--workspace-border-default);
200
+ overflow-y: auto;
201
+ display: flex;
202
+ flex-direction: column;
203
+ }
204
+
205
+ .controls-header {
206
+ padding: 16px 20px;
207
+ border-bottom: 1px solid var(--workspace-border-subtle);
208
+ display: flex;
209
+ justify-content: space-between;
210
+ align-items: center;
211
+ background: var(--bg-surface);
212
+ position: sticky;
213
+ top: 0;
214
+ z-index: 10;
215
+ }
216
+
217
+ .controls-header h1 {
218
+ font-size: 1.1em;
219
+ font-weight: 600;
220
+ color: var(--status-success);
221
+ }
222
+
223
+ .controls-body {
224
+ padding: 0 20px 20px;
225
+ flex: 1;
226
+ }
227
+
228
+ .filename {
229
+ font-size: 0.8em;
230
+ color: var(--text-muted);
231
+ margin-top: 4px;
232
+ word-break: break-all;
233
+ }
234
+
235
+ /* Theme toggle */
236
+ .theme-toggle {
237
+ background: transparent;
238
+ border: 1px solid var(--border-muted);
239
+ color: var(--text-secondary);
240
+ cursor: pointer;
241
+ font-size: 16px;
242
+ padding: 6px 10px;
243
+ border-radius: 6px;
244
+ transition: all 0.2s;
245
+ display: flex;
246
+ align-items: center;
247
+ gap: 6px;
248
+ }
249
+
250
+ .theme-toggle:hover {
251
+ background: var(--bg-muted);
252
+ border-color: var(--border-default);
253
+ }
254
+
255
+ /* =============================================================================
256
+ * Section Headers
257
+ * ============================================================================= */
258
+ .section {
259
+ margin-top: 16px;
260
+ }
261
+
262
+ .section-header {
263
+ font-size: 0.75em;
264
+ font-weight: 600;
265
+ text-transform: uppercase;
266
+ letter-spacing: 0.5px;
267
+ color: var(--text-inverse);
268
+ background: var(--status-success);
269
+ padding: 8px 12px;
270
+ border-radius: 4px;
271
+ margin-bottom: 12px;
272
+ }
273
+
274
+ /* =============================================================================
275
+ * Form Fields
276
+ * ============================================================================= */
277
+ .field { margin-bottom: 12px; }
278
+
279
+ .field label {
280
+ display: block;
281
+ font-size: 0.8em;
282
+ font-weight: 500;
283
+ margin-bottom: 4px;
284
+ color: var(--text-secondary);
285
+ }
286
+
287
+ .field input[type="text"],
288
+ .field input[type="number"],
289
+ .field select {
290
+ width: 100%;
291
+ padding: 8px 10px;
292
+ border: 1px solid var(--border-muted);
293
+ border-radius: 4px;
294
+ background: var(--bg-surface);
295
+ color: var(--text-primary);
296
+ font-size: 0.85em;
297
+ transition: border-color 0.2s;
298
+ }
299
+
300
+ .field input:focus,
301
+ .field select:focus {
302
+ outline: none;
303
+ border-color: var(--status-success);
304
+ }
305
+
306
+ .field input[type="color"] {
307
+ width: 40px;
308
+ height: 32px;
309
+ padding: 2px;
310
+ border: 1px solid var(--border-muted);
311
+ border-radius: 4px;
312
+ cursor: pointer;
313
+ background: var(--bg-surface);
314
+ }
315
+
316
+ .field-row {
317
+ display: flex;
318
+ gap: 10px;
319
+ }
320
+
321
+ .field-row .field { flex: 1; }
322
+
323
+ /* Checkbox styling */
324
+ .checkbox-field {
325
+ display: flex;
326
+ align-items: center;
327
+ gap: 8px;
328
+ cursor: pointer;
329
+ padding: 6px 0;
330
+ }
331
+
332
+ .checkbox-field input[type="checkbox"] {
333
+ width: 16px;
334
+ height: 16px;
335
+ accent-color: var(--status-success);
336
+ }
337
+
338
+ .checkbox-field span {
339
+ font-size: 0.85em;
340
+ color: var(--text-primary);
341
+ }
342
+
343
+ /* Color field with input */
344
+ .color-field {
345
+ display: flex;
346
+ align-items: center;
347
+ gap: 8px;
348
+ }
349
+
350
+ .color-field input[type="text"] {
351
+ flex: 1;
352
+ }
353
+
354
+ /* =============================================================================
355
+ * Traces Section
356
+ * ============================================================================= */
357
+ .traces-list {
358
+ max-height: 200px;
359
+ overflow-y: auto;
360
+ border: 1px solid var(--border-muted);
361
+ border-radius: 4px;
362
+ background: var(--bg-muted);
363
+ }
364
+
365
+ .trace-item {
366
+ display: flex;
367
+ align-items: center;
368
+ gap: 8px;
369
+ padding: 8px 10px;
370
+ border-bottom: 1px solid var(--border-muted);
371
+ font-size: 0.85em;
372
+ }
373
+
374
+ .trace-item:last-child { border-bottom: none; }
375
+
376
+ .trace-color {
377
+ width: 24px;
378
+ height: 24px;
379
+ border-radius: 4px;
380
+ border: 1px solid var(--border-default);
381
+ cursor: pointer;
382
+ }
383
+
384
+ .trace-label {
385
+ flex: 1;
386
+ color: var(--text-primary);
387
+ }
388
+
389
+ .trace-style select {
390
+ padding: 4px 6px;
391
+ font-size: 0.8em;
392
+ border: 1px solid var(--border-muted);
393
+ border-radius: 3px;
394
+ background: var(--bg-surface);
395
+ color: var(--text-primary);
396
+ }
397
+
398
+ /* =============================================================================
399
+ * Annotations
400
+ * ============================================================================= */
401
+ .annotations-list {
402
+ margin-top: 10px;
403
+ max-height: 120px;
404
+ overflow-y: auto;
405
+ }
406
+
407
+ .annotation-item {
408
+ display: flex;
409
+ justify-content: space-between;
410
+ align-items: center;
411
+ padding: 6px 10px;
412
+ background: var(--bg-muted);
413
+ border-radius: 4px;
414
+ margin-bottom: 5px;
415
+ font-size: 0.85em;
416
+ }
417
+
418
+ .annotation-item span { color: var(--text-primary); }
419
+
420
+ .annotation-item button {
421
+ padding: 3px 8px;
422
+ font-size: 0.75em;
423
+ background: var(--status-error);
424
+ border: none;
425
+ border-radius: 3px;
426
+ color: white;
427
+ cursor: pointer;
428
+ }
429
+
430
+ /* =============================================================================
431
+ * Buttons
432
+ * ============================================================================= */
433
+ .btn {
434
+ width: 100%;
435
+ padding: 10px 16px;
436
+ margin-top: 8px;
437
+ border: none;
438
+ border-radius: 4px;
439
+ cursor: pointer;
440
+ font-size: 0.9em;
441
+ font-weight: 500;
442
+ transition: all 0.2s;
443
+ }
444
+
445
+ .btn-primary {
446
+ background: var(--status-success);
447
+ color: white;
448
+ }
449
+
450
+ .btn-primary:hover {
451
+ filter: brightness(1.1);
452
+ }
453
+
454
+ .btn-secondary {
455
+ background: var(--bg-muted);
456
+ color: var(--text-primary);
457
+ border: 1px solid var(--border-muted);
458
+ }
459
+
460
+ .btn-secondary:hover {
461
+ background: var(--workspace-bg-tertiary);
462
+ }
463
+
464
+ .btn-cta {
465
+ background: var(--color-cta);
466
+ color: white;
467
+ }
468
+
469
+ .btn-cta:hover {
470
+ background: var(--color-cta-hover);
471
+ }
472
+
473
+ /* =============================================================================
474
+ * Status Bar
475
+ * ============================================================================= */
476
+ .status-bar {
477
+ margin-top: 16px;
478
+ padding: 10px 12px;
479
+ border-radius: 4px;
480
+ background: var(--bg-muted);
481
+ font-size: 0.8em;
482
+ color: var(--text-secondary);
483
+ border-left: 3px solid var(--status-success);
484
+ }
485
+
486
+ .status-bar.error {
487
+ border-left-color: var(--status-error);
488
+ }
489
+
490
+ /* =============================================================================
491
+ * Collapsible Sections
492
+ * ============================================================================= */
493
+ .section-toggle {
494
+ cursor: pointer;
495
+ display: flex;
496
+ align-items: center;
497
+ gap: 8px;
498
+ }
499
+
500
+ .section-toggle::before {
501
+ content: "\\25BC";
502
+ font-size: 0.7em;
503
+ transition: transform 0.2s;
504
+ }
505
+
506
+ .section-toggle.collapsed::before {
507
+ transform: rotate(-90deg);
508
+ }
509
+
510
+ .section-content {
511
+ overflow: hidden;
512
+ transition: max-height 0.3s ease;
513
+ }
514
+
515
+ .section-content.collapsed {
516
+ max-height: 0 !important;
517
+ }
518
+
519
+ /* =============================================================================
520
+ * Scrollbar Styling
521
+ * ============================================================================= */
522
+ .controls::-webkit-scrollbar,
523
+ .traces-list::-webkit-scrollbar,
524
+ .annotations-list::-webkit-scrollbar {
525
+ width: 6px;
526
+ }
527
+
528
+ .controls::-webkit-scrollbar-track,
529
+ .traces-list::-webkit-scrollbar-track,
530
+ .annotations-list::-webkit-scrollbar-track {
531
+ background: var(--bg-muted);
532
+ }
533
+
534
+ .controls::-webkit-scrollbar-thumb,
535
+ .traces-list::-webkit-scrollbar-thumb,
536
+ .annotations-list::-webkit-scrollbar-thumb {
537
+ background: var(--border-default);
538
+ border-radius: 3px;
539
+ }
540
+
541
+ .controls::-webkit-scrollbar-thumb:hover,
542
+ .traces-list::-webkit-scrollbar-thumb:hover,
543
+ .annotations-list::-webkit-scrollbar-thumb:hover {
544
+ background: var(--text-muted);
545
+ }
546
+ '''
547
+
548
+
549
+ # EOF
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # File: ./src/scitex/vis/editor/flask_editor/utils.py
4
+ """Port management utilities for Flask editor."""
5
+
6
+ import socket
7
+ import subprocess
8
+ import sys
9
+
10
+
11
+ def find_available_port(start_port: int = 5050, max_attempts: int = 10) -> int:
12
+ """Find an available port, starting from start_port."""
13
+ for offset in range(max_attempts):
14
+ port = start_port + offset
15
+ try:
16
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
17
+ s.bind(('127.0.0.1', port))
18
+ return port
19
+ except OSError:
20
+ continue
21
+
22
+ raise RuntimeError(
23
+ f"Could not find available port in range {start_port}-{start_port + max_attempts}"
24
+ )
25
+
26
+
27
+ def kill_process_on_port(port: int) -> bool:
28
+ """Try to kill process using the specified port. Returns True if successful."""
29
+ try:
30
+ if sys.platform == 'win32':
31
+ # Windows: netstat + taskkill
32
+ result = subprocess.run(
33
+ f'netstat -ano | findstr :{port}',
34
+ shell=True, capture_output=True, text=True
35
+ )
36
+ if result.stdout:
37
+ for line in result.stdout.strip().split('\n'):
38
+ parts = line.split()
39
+ if len(parts) >= 5:
40
+ pid = parts[-1]
41
+ subprocess.run(
42
+ f'taskkill /F /PID {pid}',
43
+ shell=True, capture_output=True
44
+ )
45
+ return True
46
+ else:
47
+ # Linux/Mac: fuser or lsof
48
+ result = subprocess.run(
49
+ ['fuser', '-k', f'{port}/tcp'],
50
+ capture_output=True, text=True
51
+ )
52
+ if result.returncode == 0:
53
+ return True
54
+
55
+ # Fallback to lsof
56
+ result = subprocess.run(
57
+ ['lsof', '-t', f'-i:{port}'],
58
+ capture_output=True, text=True
59
+ )
60
+ if result.stdout:
61
+ for pid in result.stdout.strip().split('\n'):
62
+ if pid:
63
+ subprocess.run(['kill', '-9', pid], capture_output=True)
64
+ return True
65
+ except Exception:
66
+ pass
67
+
68
+ return False
69
+
70
+
71
+ def check_port_available(port: int) -> bool:
72
+ """Check if a port is available."""
73
+ try:
74
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
75
+ s.bind(('127.0.0.1', port))
76
+ return True
77
+ except OSError:
78
+ return False
79
+
80
+
81
+ # EOF