scriptplan 0.9.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 (49) hide show
  1. scriptplan/__init__.py +22 -0
  2. scriptplan/cli/__init__.py +7 -0
  3. scriptplan/cli/main.py +546 -0
  4. scriptplan/core/__init__.py +0 -0
  5. scriptplan/core/account.py +125 -0
  6. scriptplan/core/allocation.py +69 -0
  7. scriptplan/core/booking.py +39 -0
  8. scriptplan/core/journal.py +377 -0
  9. scriptplan/core/leave.py +14 -0
  10. scriptplan/core/limits.py +354 -0
  11. scriptplan/core/project.py +924 -0
  12. scriptplan/core/property.py +1290 -0
  13. scriptplan/core/resource.py +198 -0
  14. scriptplan/core/resource_scenario.py +711 -0
  15. scriptplan/core/scenario.py +5 -0
  16. scriptplan/core/scenario_data.py +39 -0
  17. scriptplan/core/shift.py +71 -0
  18. scriptplan/core/task.py +77 -0
  19. scriptplan/core/task_scenario.py +1515 -0
  20. scriptplan/core/timesheet.py +457 -0
  21. scriptplan/core/working_hours.py +231 -0
  22. scriptplan/parser/__init__.py +0 -0
  23. scriptplan/parser/macro_processor.py +264 -0
  24. scriptplan/parser/tjp.lark +412 -0
  25. scriptplan/parser/tjp_parser.py +1904 -0
  26. scriptplan/py.typed +0 -0
  27. scriptplan/report/__init__.py +75 -0
  28. scriptplan/report/html_generator.py +477 -0
  29. scriptplan/report/report.py +466 -0
  30. scriptplan/report/report_base.py +397 -0
  31. scriptplan/report/report_context.py +248 -0
  32. scriptplan/report/resource_report.py +341 -0
  33. scriptplan/report/table_report.py +693 -0
  34. scriptplan/report/task_report.py +362 -0
  35. scriptplan/report/text_report.py +172 -0
  36. scriptplan/scheduler/__init__.py +0 -0
  37. scriptplan/scheduler/batch_processor.py +238 -0
  38. scriptplan/scheduler/scoreboard.py +120 -0
  39. scriptplan/utils/__init__.py +0 -0
  40. scriptplan/utils/data_cache.py +46 -0
  41. scriptplan/utils/logger.py +243 -0
  42. scriptplan/utils/message_handler.py +515 -0
  43. scriptplan/utils/time.py +195 -0
  44. scriptplan-0.9.0.dist-info/METADATA +161 -0
  45. scriptplan-0.9.0.dist-info/RECORD +49 -0
  46. scriptplan-0.9.0.dist-info/WHEEL +5 -0
  47. scriptplan-0.9.0.dist-info/entry_points.txt +2 -0
  48. scriptplan-0.9.0.dist-info/licenses/LICENSE +201 -0
  49. scriptplan-0.9.0.dist-info/top_level.txt +1 -0
scriptplan/py.typed ADDED
File without changes
@@ -0,0 +1,75 @@
1
+ """
2
+ Reporting System for ScriptPlan.
3
+
4
+ This package provides a comprehensive reporting system for generating various
5
+ types of reports from scheduled project data. Reports can be output in multiple
6
+ formats including HTML, CSV, iCal, and others.
7
+
8
+ Main Classes:
9
+ - Report: Base class for all report definitions
10
+ - ReportContext: Context management during report generation
11
+ - ReportBase: Abstract base for report content generators
12
+ - TableReport: Base for tabular reports
13
+ - TaskReport: Task listing reports
14
+ - ResourceReport: Resource listing reports
15
+ - TextReport: Simple text-based reports
16
+
17
+ Usage:
18
+ from scriptplan.report import Report, ReportType, ReportFormat
19
+
20
+ # Create a report
21
+ report = Report(project, 'task_list', 'Task List', None)
22
+ report.type_spec = ReportType.TASK_REPORT
23
+
24
+ # Generate output
25
+ report.generate([ReportFormat.HTML, ReportFormat.CSV])
26
+ """
27
+
28
+ from scriptplan.report.report_context import (
29
+ ReportContext,
30
+ Query,
31
+ )
32
+ from scriptplan.report.report import (
33
+ Report,
34
+ ReportFormat,
35
+ ReportType,
36
+ ReportScenario,
37
+ )
38
+ from scriptplan.report.report_base import ReportBase
39
+ from scriptplan.report.table_report import (
40
+ TableReport,
41
+ ReportTable,
42
+ ReportTableLine,
43
+ ReportTableCell,
44
+ ReportTableColumn,
45
+ ReportTableLegend,
46
+ Alignment,
47
+ )
48
+ from scriptplan.report.task_report import TaskReport
49
+ from scriptplan.report.resource_report import ResourceReport
50
+ from scriptplan.report.text_report import TextReport
51
+
52
+ __all__ = [
53
+ # Context
54
+ 'ReportContext',
55
+ 'Query',
56
+ # Report definition
57
+ 'Report',
58
+ 'ReportFormat',
59
+ 'ReportType',
60
+ 'ReportScenario',
61
+ # Base classes
62
+ 'ReportBase',
63
+ 'TableReport',
64
+ # Table components
65
+ 'ReportTable',
66
+ 'ReportTableLine',
67
+ 'ReportTableCell',
68
+ 'ReportTableColumn',
69
+ 'ReportTableLegend',
70
+ 'Alignment',
71
+ # Report types
72
+ 'TaskReport',
73
+ 'ResourceReport',
74
+ 'TextReport',
75
+ ]
@@ -0,0 +1,477 @@
1
+ """
2
+ HTML Generator - Complete HTML document generation for reports.
3
+
4
+ This module provides utilities for generating complete HTML documents
5
+ with proper CSS styling, similar to TaskJuggler's output.
6
+ """
7
+
8
+ from typing import Optional, Dict, Any, List
9
+ from datetime import datetime
10
+
11
+
12
+ def get_default_css() -> str:
13
+ """Get the default CSS for TaskJuggler-style reports."""
14
+ return """
15
+ /* TaskJuggler Style CSS */
16
+ :root {
17
+ --tj-primary: #4a90d9;
18
+ --tj-primary-dark: #2c6eb3;
19
+ --tj-header-bg: #4a5568;
20
+ --tj-header-text: #ffffff;
21
+ --tj-border: #e2e8f0;
22
+ --tj-row-alt: #f7fafc;
23
+ --tj-row-hover: #edf2f7;
24
+ --tj-text: #2d3748;
25
+ --tj-text-muted: #718096;
26
+ --tj-success: #48bb78;
27
+ --tj-warning: #ed8936;
28
+ --tj-danger: #f56565;
29
+ }
30
+
31
+ * {
32
+ box-sizing: border-box;
33
+ }
34
+
35
+ body {
36
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
37
+ font-size: 14px;
38
+ line-height: 1.5;
39
+ color: var(--tj-text);
40
+ background-color: #f5f5f5;
41
+ margin: 0;
42
+ padding: 0;
43
+ }
44
+
45
+ .tj_container {
46
+ max-width: 1400px;
47
+ margin: 0 auto;
48
+ padding: 20px;
49
+ background: white;
50
+ min-height: 100vh;
51
+ }
52
+
53
+ /* Header Styles */
54
+ .tj_page_header {
55
+ background: var(--tj-header-bg);
56
+ color: var(--tj-header-text);
57
+ padding: 20px 30px;
58
+ margin: -20px -20px 20px -20px;
59
+ }
60
+
61
+ .tj_page_header h1 {
62
+ margin: 0 0 5px 0;
63
+ font-size: 24px;
64
+ font-weight: 600;
65
+ }
66
+
67
+ .tj_page_header .subtitle {
68
+ font-size: 14px;
69
+ opacity: 0.9;
70
+ }
71
+
72
+ /* Navigation */
73
+ .tj_navigation {
74
+ background: #f8f9fa;
75
+ border-bottom: 1px solid var(--tj-border);
76
+ padding: 10px 20px;
77
+ margin: -20px -20px 20px -20px;
78
+ }
79
+
80
+ .tj_navigation a {
81
+ color: var(--tj-primary);
82
+ text-decoration: none;
83
+ margin-right: 20px;
84
+ font-size: 13px;
85
+ }
86
+
87
+ .tj_navigation a:hover {
88
+ text-decoration: underline;
89
+ }
90
+
91
+ .tj_navigation a.active {
92
+ font-weight: 600;
93
+ }
94
+
95
+ /* Table Styles */
96
+ .tj_report_table {
97
+ width: 100%;
98
+ border-collapse: collapse;
99
+ border: 1px solid var(--tj-border);
100
+ font-size: 13px;
101
+ }
102
+
103
+ .tj_report_table thead {
104
+ background: var(--tj-header-bg);
105
+ }
106
+
107
+ .tj_report_table th {
108
+ color: var(--tj-header-text);
109
+ font-weight: 600;
110
+ text-align: left;
111
+ padding: 12px 15px;
112
+ border-bottom: 2px solid var(--tj-primary-dark);
113
+ white-space: nowrap;
114
+ }
115
+
116
+ .tj_report_table td {
117
+ padding: 10px 15px;
118
+ border-bottom: 1px solid var(--tj-border);
119
+ vertical-align: middle;
120
+ }
121
+
122
+ .tj_report_table tbody tr:nth-child(even) {
123
+ background-color: var(--tj-row-alt);
124
+ }
125
+
126
+ .tj_report_table tbody tr:hover {
127
+ background-color: var(--tj-row-hover);
128
+ }
129
+
130
+ /* Task hierarchy indentation */
131
+ .tj_report_table .indent-0 { padding-left: 15px; }
132
+ .tj_report_table .indent-1 { padding-left: 35px; }
133
+ .tj_report_table .indent-2 { padding-left: 55px; }
134
+ .tj_report_table .indent-3 { padding-left: 75px; }
135
+ .tj_report_table .indent-4 { padding-left: 95px; }
136
+
137
+ /* Row types */
138
+ .tj_report_table tr.container_task {
139
+ font-weight: 600;
140
+ background-color: #f0f4f8 !important;
141
+ }
142
+
143
+ .tj_report_table tr.milestone {
144
+ font-style: italic;
145
+ }
146
+
147
+ .tj_report_table tr.nested_resource {
148
+ background-color: #fafbfc !important;
149
+ font-size: 12px;
150
+ }
151
+
152
+ /* Table Frame */
153
+ .tj_table_frame {
154
+ width: 100%;
155
+ border-collapse: collapse;
156
+ margin-bottom: 20px;
157
+ }
158
+
159
+ .tj_table_frame td {
160
+ padding: 0;
161
+ }
162
+
163
+ /* Headline */
164
+ .tj_table_headline {
165
+ font-size: 18px;
166
+ font-weight: 600;
167
+ color: var(--tj-text);
168
+ padding: 15px 0;
169
+ border-bottom: 2px solid var(--tj-primary);
170
+ margin-bottom: 15px;
171
+ }
172
+
173
+ /* Caption */
174
+ .tj_table_caption {
175
+ font-style: italic;
176
+ color: var(--tj-text-muted);
177
+ padding: 15px 0;
178
+ border-top: 1px solid var(--tj-border);
179
+ margin-top: 15px;
180
+ }
181
+
182
+ /* Legend */
183
+ .tj_table_legend {
184
+ margin-top: 20px;
185
+ padding: 15px;
186
+ background: #f8f9fa;
187
+ border-radius: 4px;
188
+ }
189
+
190
+ .tj_table_legend table {
191
+ border: none;
192
+ }
193
+
194
+ .tj_table_legend td {
195
+ padding: 5px 15px 5px 0;
196
+ vertical-align: middle;
197
+ }
198
+
199
+ /* Alert Colors */
200
+ .alert-green {
201
+ color: var(--tj-success);
202
+ }
203
+
204
+ .alert-yellow {
205
+ color: var(--tj-warning);
206
+ }
207
+
208
+ .alert-red {
209
+ color: var(--tj-danger);
210
+ }
211
+
212
+ .alert-indicator {
213
+ display: inline-block;
214
+ width: 12px;
215
+ height: 12px;
216
+ border-radius: 50%;
217
+ margin-right: 8px;
218
+ }
219
+
220
+ .alert-indicator.green { background-color: var(--tj-success); }
221
+ .alert-indicator.yellow { background-color: var(--tj-warning); }
222
+ .alert-indicator.red { background-color: var(--tj-danger); }
223
+
224
+ /* Gantt Chart Placeholder */
225
+ .tj_gantt_bar {
226
+ height: 18px;
227
+ background: var(--tj-primary);
228
+ border-radius: 3px;
229
+ position: relative;
230
+ }
231
+
232
+ .tj_gantt_bar.milestone {
233
+ width: 12px;
234
+ height: 12px;
235
+ background: var(--tj-text);
236
+ transform: rotate(45deg);
237
+ margin: 3px auto;
238
+ }
239
+
240
+ .tj_gantt_container {
241
+ position: relative;
242
+ height: 24px;
243
+ }
244
+
245
+ /* Text Report Sections */
246
+ .tj_header, .tj_footer {
247
+ padding: 15px 0;
248
+ }
249
+
250
+ .tj_headline {
251
+ font-size: 20px;
252
+ font-weight: 600;
253
+ padding: 20px 0 10px 0;
254
+ border-bottom: 2px solid var(--tj-primary);
255
+ margin-bottom: 20px;
256
+ }
257
+
258
+ .tj_columns {
259
+ display: flex;
260
+ gap: 30px;
261
+ margin: 20px 0;
262
+ }
263
+
264
+ .tj_left, .tj_center, .tj_right {
265
+ flex: 1;
266
+ }
267
+
268
+ .tj_center { text-align: center; }
269
+ .tj_right { text-align: right; }
270
+
271
+ /* Journal Entries */
272
+ .tj_journal_entry {
273
+ border-left: 4px solid var(--tj-border);
274
+ padding: 10px 15px;
275
+ margin: 10px 0;
276
+ }
277
+
278
+ .tj_journal_entry.alert-green { border-left-color: var(--tj-success); }
279
+ .tj_journal_entry.alert-yellow { border-left-color: var(--tj-warning); }
280
+ .tj_journal_entry.alert-red { border-left-color: var(--tj-danger); }
281
+
282
+ .tj_journal_date {
283
+ font-size: 12px;
284
+ color: var(--tj-text-muted);
285
+ }
286
+
287
+ .tj_journal_headline {
288
+ font-weight: 600;
289
+ margin: 5px 0;
290
+ }
291
+
292
+ .tj_journal_author {
293
+ font-size: 12px;
294
+ color: var(--tj-text-muted);
295
+ }
296
+
297
+ .tj_journal_summary {
298
+ margin-top: 10px;
299
+ }
300
+
301
+ /* Footer */
302
+ .tj_page_footer {
303
+ margin-top: 40px;
304
+ padding-top: 20px;
305
+ border-top: 1px solid var(--tj-border);
306
+ text-align: center;
307
+ font-size: 12px;
308
+ color: var(--tj-text-muted);
309
+ }
310
+
311
+ /* Print Styles */
312
+ @media print {
313
+ body {
314
+ background: white;
315
+ }
316
+
317
+ .tj_container {
318
+ padding: 0;
319
+ max-width: none;
320
+ }
321
+
322
+ .tj_navigation {
323
+ display: none;
324
+ }
325
+
326
+ .tj_report_table {
327
+ font-size: 11px;
328
+ }
329
+
330
+ .tj_report_table th,
331
+ .tj_report_table td {
332
+ padding: 6px 10px;
333
+ }
334
+ }
335
+ """
336
+
337
+
338
+ def build_html_document(title: str, content: str,
339
+ project_name: str = '',
340
+ subtitle: str = '',
341
+ navigation: Optional[List[Dict[str, str]]] = None,
342
+ include_css: bool = True,
343
+ custom_css: str = '',
344
+ footer: bool = True) -> str:
345
+ """
346
+ Build a complete HTML document for a report.
347
+
348
+ Args:
349
+ title: Page title
350
+ content: Main content HTML
351
+ project_name: Project name for header
352
+ subtitle: Subtitle for header
353
+ navigation: List of nav items [{'title': '...', 'url': '...', 'active': bool}]
354
+ include_css: Whether to include default CSS
355
+ custom_css: Additional CSS to include
356
+ footer: Whether to include footer
357
+
358
+ Returns:
359
+ Complete HTML document string
360
+ """
361
+ css = ''
362
+ if include_css:
363
+ css = f'<style>{get_default_css()}</style>'
364
+ if custom_css:
365
+ css += f'<style>{custom_css}</style>'
366
+
367
+ nav_html = ''
368
+ if navigation:
369
+ nav_items = []
370
+ for item in navigation:
371
+ active = ' class="active"' if item.get('active') else ''
372
+ nav_items.append(f'<a href="{item["url"]}"{active}>{item["title"]}</a>')
373
+ nav_html = f'<nav class="tj_navigation">{" ".join(nav_items)}</nav>'
374
+
375
+ header_html = ''
376
+ if project_name or subtitle:
377
+ header_html = f'''
378
+ <header class="tj_page_header">
379
+ <h1>{project_name or title}</h1>
380
+ {f'<div class="subtitle">{subtitle}</div>' if subtitle else ''}
381
+ </header>
382
+ '''
383
+
384
+ footer_html = ''
385
+ if footer:
386
+ now = datetime.now().strftime('%Y-%m-%d %H:%M')
387
+ footer_html = f'''
388
+ <footer class="tj_page_footer">
389
+ Generated by ScriptPlan on {now}
390
+ </footer>
391
+ '''
392
+
393
+ return f'''<!DOCTYPE html>
394
+ <html lang="en">
395
+ <head>
396
+ <meta charset="UTF-8">
397
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
398
+ <meta name="generator" content="ScriptPlan">
399
+ <title>{title}</title>
400
+ {css}
401
+ </head>
402
+ <body>
403
+ <div class="tj_container">
404
+ {header_html}
405
+ {nav_html}
406
+ <main class="tj_content">
407
+ {content}
408
+ </main>
409
+ {footer_html}
410
+ </div>
411
+ </body>
412
+ </html>'''
413
+
414
+
415
+ def format_date(date: datetime, fmt: str = '%Y-%m-%d') -> str:
416
+ """Format a date for display."""
417
+ if date is None:
418
+ return ''
419
+ return date.strftime(fmt)
420
+
421
+
422
+ def format_duration(hours: float, unit: str = 'days') -> str:
423
+ """Format a duration for display."""
424
+ if hours is None:
425
+ return ''
426
+
427
+ if unit == 'hours':
428
+ return f'{hours:.1f}h'
429
+ elif unit == 'days':
430
+ days = hours / 8.0
431
+ return f'{days:.1f}d'
432
+ elif unit == 'weeks':
433
+ weeks = hours / 40.0
434
+ return f'{weeks:.1f}w'
435
+ else:
436
+ return f'{hours:.1f}h'
437
+
438
+
439
+ def format_percentage(value: float) -> str:
440
+ """Format a percentage for display."""
441
+ if value is None:
442
+ return ''
443
+ return f'{value:.0f}%'
444
+
445
+
446
+ def alert_indicator_html(level: str) -> str:
447
+ """Generate HTML for an alert level indicator."""
448
+ level_lower = level.lower() if level else 'green'
449
+ return f'<span class="alert-indicator {level_lower}"></span>'
450
+
451
+
452
+ def journal_entry_html(entry: Any) -> str:
453
+ """
454
+ Generate HTML for a journal entry.
455
+
456
+ Args:
457
+ entry: JournalEntry object
458
+
459
+ Returns:
460
+ HTML string for the entry
461
+ """
462
+ alert_class = f'alert-{entry.alert_level.name.lower()}' if hasattr(entry, 'alert_level') else ''
463
+ date_str = format_date(entry.date, '%Y-%m-%d %H:%M') if entry.date else ''
464
+ author_str = f'by {entry.author.name}' if hasattr(entry, 'author') and entry.author else ''
465
+
466
+ summary_html = ''
467
+ if hasattr(entry, 'summary') and entry.summary:
468
+ summary_html = f'<div class="tj_journal_summary">{entry.summary}</div>'
469
+
470
+ return f'''
471
+ <div class="tj_journal_entry {alert_class}">
472
+ <div class="tj_journal_date">{date_str}</div>
473
+ <div class="tj_journal_headline">{entry.headline}</div>
474
+ {f'<div class="tj_journal_author">{author_str}</div>' if author_str else ''}
475
+ {summary_html}
476
+ </div>
477
+ '''