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
@@ -0,0 +1,264 @@
1
+ """Macro preprocessor for TJP files.
2
+
3
+ Handles macro definitions and expansions before the main parser runs.
4
+ """
5
+
6
+ import re
7
+ from typing import Dict, Optional, List, Tuple
8
+ from datetime import datetime
9
+
10
+
11
+ def strip_shell_comments(text: str) -> str:
12
+ """Strip shell-style comments from text, preserving strings.
13
+
14
+ Shell comments start with # and continue to end of line.
15
+ Comments inside strings (single or double quoted) are preserved.
16
+ """
17
+ result = []
18
+ i = 0
19
+ n = len(text)
20
+
21
+ while i < n:
22
+ # Check for strings - preserve them entirely
23
+ if text[i] in '"\'':
24
+ quote = text[i]
25
+ result.append(text[i])
26
+ i += 1
27
+ while i < n and text[i] != quote:
28
+ result.append(text[i])
29
+ i += 1
30
+ if i < n:
31
+ result.append(text[i]) # closing quote
32
+ i += 1
33
+ # Check for shell comment
34
+ elif text[i] == '#':
35
+ # Skip until end of line
36
+ while i < n and text[i] != '\n':
37
+ i += 1
38
+ # Keep the newline
39
+ if i < n:
40
+ result.append(text[i])
41
+ i += 1
42
+ else:
43
+ result.append(text[i])
44
+ i += 1
45
+
46
+ return ''.join(result)
47
+
48
+
49
+ class MacroProcessor:
50
+ """Preprocesses TJP content to expand macros.
51
+
52
+ TJP macros have the form:
53
+ - Definition: macro name [ content ]
54
+ - Usage: ${name} or ${name arg1 arg2 ...}
55
+
56
+ Built-in macros:
57
+ - ${projectstart} - project start date
58
+ - ${projectend} - project end date
59
+ - ${now} - current date
60
+ - ${today} - today's date
61
+ """
62
+
63
+ def __init__(self):
64
+ self._macros: Dict[str, str] = {}
65
+ self._project_start: Optional[str] = None
66
+ self._project_end: Optional[str] = None
67
+ self._now: Optional[str] = None
68
+
69
+ def process(self, content: str) -> str:
70
+ """Process TJP content, extracting macro definitions and expanding macro calls.
71
+
72
+ Args:
73
+ content: The raw TJP file content
74
+
75
+ Returns:
76
+ The processed content with macros expanded
77
+ """
78
+ # First pass: extract macro definitions
79
+ content = self._extract_macros(content)
80
+
81
+ # Extract project dates for built-in macros
82
+ self._extract_project_dates(content)
83
+
84
+ # Second pass: expand macro calls
85
+ content = self._expand_macros(content)
86
+
87
+ return content
88
+
89
+ def _extract_macros(self, content: str) -> str:
90
+ """Extract macro definitions from content.
91
+
92
+ Macro syntax: macro name [ content ]
93
+ The content can span multiple lines and contain nested brackets.
94
+ """
95
+ result = []
96
+ i = 0
97
+ n = len(content)
98
+
99
+ while i < n:
100
+ # Look for 'macro' keyword
101
+ match = re.match(r'\s*macro\s+(\w+)\s*\[', content[i:])
102
+ if match:
103
+ macro_name = match.group(1)
104
+ start_pos = i + match.end()
105
+
106
+ # Find the matching closing bracket
107
+ bracket_count = 1
108
+ j = start_pos
109
+ while j < n and bracket_count > 0:
110
+ if content[j] == '[':
111
+ bracket_count += 1
112
+ elif content[j] == ']':
113
+ bracket_count -= 1
114
+ j += 1
115
+
116
+ if bracket_count == 0:
117
+ # Extract macro content (excluding the brackets)
118
+ # Strip shell comments to avoid issues with comment eating
119
+ # parts of the expanded content
120
+ macro_content = content[start_pos:j - 1]
121
+ macro_content = strip_shell_comments(macro_content)
122
+ self._macros[macro_name] = macro_content.strip()
123
+ i = j
124
+ continue
125
+
126
+ result.append(content[i])
127
+ i += 1
128
+
129
+ return ''.join(result)
130
+
131
+ def _extract_project_dates(self, content: str):
132
+ """Extract project start/end dates for built-in macros."""
133
+ # Look for project declaration: project id "name" date +duration
134
+ match = re.search(r'project\s+\w+\s+"[^"]*"\s+(\d{4}-\d{2}-\d{2})(?:\s+\+(\d+)([dwmy]))?', content)
135
+ if match:
136
+ self._project_start = match.group(1)
137
+ # Calculate project end from duration if present
138
+ if match.group(2) and match.group(3):
139
+ from dateutil.relativedelta import relativedelta
140
+ start_date = datetime.strptime(match.group(1), '%Y-%m-%d')
141
+ amount = int(match.group(2))
142
+ unit = match.group(3)
143
+ if unit == 'd':
144
+ end_date = start_date + relativedelta(days=amount)
145
+ elif unit == 'w':
146
+ end_date = start_date + relativedelta(weeks=amount)
147
+ elif unit == 'm':
148
+ end_date = start_date + relativedelta(months=amount)
149
+ elif unit == 'y':
150
+ end_date = start_date + relativedelta(years=amount)
151
+ else:
152
+ end_date = start_date
153
+ self._project_end = end_date.strftime('%Y-%m-%d')
154
+
155
+ # Look for 'now' attribute
156
+ match = re.search(r'now\s+(\d{4}-\d{2}-\d{2})', content)
157
+ if match:
158
+ self._now = match.group(1)
159
+
160
+ def _expand_macros(self, content: str) -> str:
161
+ """Expand macro calls in content.
162
+
163
+ Macro call syntax: ${name} or ${name arg1 arg2 ...}
164
+ """
165
+ max_iterations = 100 # Prevent infinite loops
166
+ iteration = 0
167
+
168
+ while '${' in content and iteration < max_iterations:
169
+ iteration += 1
170
+ content = self._expand_once(content)
171
+
172
+ return content
173
+
174
+ def _expand_once(self, content: str) -> str:
175
+ """Perform one pass of macro expansion."""
176
+ result = []
177
+ i = 0
178
+ n = len(content)
179
+
180
+ while i < n:
181
+ if content[i:i + 2] == '${':
182
+ # Find the closing brace
183
+ j = i + 2
184
+ brace_count = 1
185
+ while j < n and brace_count > 0:
186
+ if content[j] == '{':
187
+ brace_count += 1
188
+ elif content[j] == '}':
189
+ brace_count -= 1
190
+ j += 1
191
+
192
+ if brace_count == 0:
193
+ # Extract macro call
194
+ macro_call = content[i + 2:j - 1].strip()
195
+ expansion = self._expand_macro_call(macro_call)
196
+ result.append(expansion)
197
+ i = j
198
+ continue
199
+
200
+ result.append(content[i])
201
+ i += 1
202
+
203
+ return ''.join(result)
204
+
205
+ def _expand_macro_call(self, call: str) -> str:
206
+ """Expand a single macro call.
207
+
208
+ Args:
209
+ call: The macro call without ${ and }
210
+
211
+ Returns:
212
+ The expanded content
213
+ """
214
+ # Parse macro name and arguments
215
+ parts = call.split()
216
+ if not parts:
217
+ return ''
218
+
219
+ name = parts[0]
220
+ args = parts[1:]
221
+
222
+ # Check for built-in macros
223
+ if name == 'projectstart':
224
+ return self._project_start or ''
225
+ elif name == 'projectend':
226
+ return self._project_end or ''
227
+ elif name == 'now':
228
+ return self._now or datetime.now().strftime('%Y-%m-%d')
229
+ elif name == 'today':
230
+ return datetime.now().strftime('%Y-%m-%d')
231
+
232
+ # Look up user-defined macro
233
+ if name in self._macros:
234
+ expansion = self._macros[name]
235
+
236
+ # Substitute arguments: $1, $2, etc.
237
+ for i, arg in enumerate(args, 1):
238
+ expansion = expansion.replace(f'${i}', arg)
239
+
240
+ return expansion
241
+
242
+ # Unknown macro - leave as is (will be handled as error later)
243
+ return f'${{{call}}}'
244
+
245
+ def get_macro(self, name: str) -> Optional[str]:
246
+ """Get a macro definition by name."""
247
+ return self._macros.get(name)
248
+
249
+ def list_macros(self) -> List[str]:
250
+ """Return list of defined macro names."""
251
+ return list(self._macros.keys())
252
+
253
+
254
+ def preprocess_tjp(content: str) -> str:
255
+ """Preprocess TJP content, expanding all macros.
256
+
257
+ Args:
258
+ content: Raw TJP file content
259
+
260
+ Returns:
261
+ Processed content with macros expanded
262
+ """
263
+ processor = MacroProcessor()
264
+ return processor.process(content)
@@ -0,0 +1,412 @@
1
+ // TJP Grammar for Lark - Extended version
2
+ // Supports a substantial subset of TaskJuggler syntax
3
+
4
+ start: statements
5
+
6
+ statements: statement*
7
+
8
+ statement: project
9
+ | global_attribute
10
+ | property_declaration
11
+ | navigator
12
+ | report_definition
13
+
14
+ // Project definition
15
+ // project_name is optional in TaskJuggler syntax
16
+ project: "project" project_id project_name? project_timeframe "{" project_attributes "}"
17
+
18
+ project_id: STRING | ID
19
+ project_name: STRING
20
+ project_timeframe: date duration_spec?
21
+
22
+ duration_spec: "+" DURATION_SPEC
23
+ DURATION_SPEC: /\d+[dwmyhmin]+/
24
+
25
+ project_attributes: project_attribute*
26
+
27
+ project_attribute: timezone
28
+ | timeformat
29
+ | numberformat
30
+ | currencyformat
31
+ | currency
32
+ | now
33
+ | scenario_def
34
+ | extend
35
+ | dailyworkinghours
36
+ | yearlyworkingdays
37
+ | weekstartsmonday
38
+ | workinghours
39
+ | timingresolution
40
+ | project_scheduling
41
+
42
+ project_scheduling: "scheduling" SCHEDULING_MODE
43
+
44
+ // Global attributes (outside project block)
45
+ global_attribute: copyright
46
+ | rate
47
+ | leaves_global
48
+ | flags_global
49
+ | balance
50
+ | vacation_global
51
+
52
+ copyright: "copyright" STRING
53
+ rate: "rate" NUMBER
54
+ leaves_global: "leaves" ID STRING date (("-" | "~") date)?
55
+ flags_global: "flags" ID ("," ID)*
56
+ balance: "balance" ID ID
57
+ vacation_global: "vacation" STRING? date ("-" date)?
58
+
59
+ // Scenario definition (within project)
60
+ // Scenarios can have optional body with nested scenarios
61
+ scenario_def: "scenario" ID STRING ("{" scenario_body "}")?
62
+ scenario_body: scenario_def*
63
+
64
+ // Extend declaration
65
+ extend: "extend" ID "{" extend_body "}"
66
+ extend_body: extend_attribute*
67
+ extend_attribute: "text" ID STRING
68
+
69
+ // Project attributes
70
+ timezone: "timezone" STRING
71
+ timeformat: "timeformat" STRING
72
+ numberformat: "numberformat" STRING STRING STRING STRING NUMBER
73
+ currencyformat: "currencyformat" STRING STRING STRING STRING NUMBER
74
+ currency: "currency" STRING
75
+ now: "now" date
76
+ dailyworkinghours: "dailyworkinghours" NUMBER
77
+ yearlyworkingdays: "yearlyworkingdays" NUMBER
78
+ weekstartsmonday: "weekstartsmonday"
79
+ timingresolution: "timingresolution" duration_value
80
+ workinghours: "workinghours" workinghours_spec
81
+ workinghours_spec: day_list duration_range ("," duration_range)*
82
+ day_list: day_spec ("," day_spec)*
83
+ day_spec: DAY_NAME ("-" DAY_NAME)? // Single day or day range like "mon - fri"
84
+ DAY_NAME.2: "mon" | "tue" | "wed" | "thu" | "fri" | "sat" | "sun"
85
+ duration_range: TIME "-" TIME
86
+ TIME: /\d{1,2}:\d{2}/
87
+
88
+ // Macro definition - disabled for now due to parser complexity
89
+ // macro_definition: "macro" ID "[" MACRO_CONTENT "]"
90
+ // MACRO_CONTENT.1: /[^\[\]]+/
91
+
92
+ // Navigator
93
+ navigator: "navigator" ID "{" navigator_body "}"
94
+ navigator_body: navigator_attr*
95
+ navigator_attr: "hidereport" (ID | FILTER_EXPR)
96
+
97
+ // Property declarations
98
+ property_declaration: resource
99
+ | task
100
+ | account
101
+ | shift
102
+
103
+ // Resource
104
+ resource: "resource" ID STRING "{" resource_body "}"
105
+ resource_body: resource_attr*
106
+
107
+ resource_attr: resource_email
108
+ | resource_rate
109
+ | resource_efficiency
110
+ | resource_managers
111
+ | resource_limits
112
+ | resource_leaves
113
+ | resource_vacation
114
+ | resource_booking
115
+ | resource_workinghours
116
+ | resource_timezone
117
+ | resource_chargeset
118
+ | resource_flags
119
+ | resource
120
+ | extended_attr
121
+
122
+ resource_email: "email" STRING
123
+ resource_rate: "rate" NUMBER
124
+ resource_efficiency: "efficiency" NUMBER
125
+ resource_managers: "managers" ID ("," ID)*
126
+ resource_limits: "limits" "{" limits_body "}"
127
+ resource_leaves: "leaves" leaves_type date ("-" date)?
128
+ resource_vacation: "vacation" date ("-" date)?
129
+ resource_booking: "booking" STRING date ("+" duration_value | duration_value)
130
+ resource_workinghours: "workinghours" (workinghours_spec | ID)
131
+ resource_timezone: "timezone" STRING
132
+ resource_chargeset: "chargeset" ID
133
+ resource_flags: "flags" ID ("," ID)*
134
+
135
+ leaves_type: "annual" | "sick" | "holiday" | "special" | "unpaid"
136
+
137
+ limits_body: limit_attr*
138
+ limit_attr: limit_dailymax | limit_weeklymax
139
+ limit_dailymax: "dailymax" duration_value limits_resources?
140
+ limit_weeklymax: "weeklymax" duration_value limits_resources?
141
+ limits_resources: "{" "resources" ID ("," ID)* "}"
142
+
143
+ // Task
144
+ task: "task" ID STRING "{" task_body "}"
145
+ task_body: task_attr*
146
+
147
+ task_attr: task_start
148
+ | task_end
149
+ | task_effort
150
+ | task_duration
151
+ | task_length
152
+ | task_milestone
153
+ | task_depends
154
+ | task_precedes
155
+ | task_allocate
156
+ | task_responsible
157
+ | task_priority
158
+ | task_complete
159
+ | task_note
160
+ | task_chargeset
161
+ | task_purge_chargeset
162
+ | task_charge
163
+ | task_limits
164
+ | task_journalentry
165
+ | task_flags
166
+ | task_scheduling
167
+ | scenario_attr
168
+ | task
169
+ | extended_attr
170
+
171
+ task_scheduling: "scheduling" SCHEDULING_MODE
172
+ SCHEDULING_MODE: "asap" | "alap"
173
+
174
+ task_start: "start" (date | MACRO_REF)
175
+ task_end: "end" date
176
+ task_effort: "effort" effort_value
177
+ task_duration: "duration" duration_value
178
+ task_length: "length" duration_value
179
+ task_milestone: "milestone"
180
+ task_depends: "depends" depends_list
181
+ task_precedes: "precedes" depends_list
182
+ task_allocate: "allocate" allocate_spec
183
+ task_responsible: "responsible" ID
184
+ task_priority: "priority" NUMBER
185
+ task_complete: "complete" NUMBER
186
+ task_note: "note" (STRING | rich_text)
187
+ task_chargeset: "chargeset" ID
188
+ task_purge_chargeset: "purge" "chargeset"
189
+ task_charge: "charge" NUMBER charge_mode
190
+ task_limits: "limits" "{" limits_body "}"
191
+ task_journalentry: "journalentry" date STRING? "{" journal_body "}"
192
+ task_flags: "flags" ID ("," ID)*
193
+
194
+ effort_value: NUMBER EFFORT_UNIT
195
+ EFFORT_UNIT: /[dwhminy]+/
196
+
197
+ duration_value: NUMBER DURATION_UNIT
198
+ DURATION_UNIT: /[dwhminy]+/
199
+
200
+ depends_list: depends_item ("," depends_item)*
201
+ depends_item: DEPENDS_REF depends_options?
202
+ depends_options: "{" depends_option* "}"
203
+ depends_option: "gapduration" duration_value -> dep_gapduration
204
+ | "gaplength" duration_value -> dep_gaplength
205
+ | "maxgapduration" duration_value -> dep_maxgapduration
206
+ | "onend" -> dep_onend
207
+ | "onstart" -> dep_onstart
208
+ DEPENDS_REF: /[!.a-zA-Z_][!.a-zA-Z0-9_]*/
209
+
210
+ allocate_spec: ID ("," ID)* allocate_options?
211
+ allocate_options: "{" allocate_option* "}"
212
+ allocate_option: "persistent"
213
+ | "mandatory"
214
+ | "alternative" ID ("," ID)*
215
+ | "limits" "{" limits_body "}"
216
+
217
+ charge_mode: "onstart" | "onend" | "perday"
218
+
219
+ journal_body: journal_attr*
220
+ journal_attr: journal_author
221
+ | journal_alert
222
+ | journal_summary
223
+ | journal_details
224
+ journal_author: "author" ID
225
+ journal_alert: "alert" alert_level
226
+ journal_summary: "summary" (STRING | rich_text)
227
+ journal_details: "details" (STRING | rich_text)
228
+ alert_level: ALERT_VALUE
229
+ ALERT_VALUE: "green" | "yellow" | "red"
230
+
231
+ scenario_attr: ID ":" scenario_specific_attr
232
+ scenario_specific_attr: scenario_start
233
+ | scenario_end
234
+ | scenario_effort
235
+ | scenario_duration
236
+ | scenario_length
237
+
238
+ scenario_start: "start" date
239
+ scenario_end: "end" date
240
+ scenario_effort: "effort" effort_value
241
+ scenario_duration: "duration" duration_value
242
+ scenario_length: "length" duration_value
243
+
244
+ // Account (body is optional for leaf accounts)
245
+ account: "account" ID STRING ("{" account_body "}")?
246
+ account_body: account_attr*
247
+ account_attr: account
248
+ | "credit" STRING
249
+ | "aggregate" ID
250
+
251
+ // Shift
252
+ shift: "shift" ID STRING? "{" shift_body "}"
253
+ shift_body: shift_attr*
254
+ shift_attr: "workinghours" workinghours_spec
255
+ | "leaves" leaves_type date ("-" date)?
256
+
257
+ // Reports
258
+ report_definition: textreport
259
+ | taskreport
260
+ | resourcereport
261
+
262
+ textreport: "textreport" ID? STRING? "{" textreport_body "}"
263
+ textreport_body: textreport_attr*
264
+ textreport_attr: textreport_header
265
+ | textreport_footer
266
+ | textreport_center
267
+ | textreport_left
268
+ | textreport_right
269
+ | textreport_formats
270
+ | textreport_title
271
+ | report_definition
272
+
273
+ textreport_header: "header" (STRING | rich_text)
274
+ textreport_footer: "footer" (STRING | rich_text)
275
+ textreport_center: "center" (STRING | rich_text)
276
+ textreport_left: "left" (STRING | rich_text)
277
+ textreport_right: "right" (STRING | rich_text)
278
+ textreport_formats: "formats" format_list
279
+ textreport_title: "title" STRING
280
+
281
+ format_list: ID ("," ID)*
282
+
283
+ taskreport: "taskreport" ID? STRING? "{" taskreport_body "}"
284
+ taskreport_body: taskreport_attr*
285
+ taskreport_attr: taskreport_header
286
+ | taskreport_footer
287
+ | taskreport_headline
288
+ | taskreport_caption
289
+ | taskreport_columns
290
+ | taskreport_timeformat
291
+ | taskreport_loadunit
292
+ | taskreport_hideresource
293
+ | taskreport_hidetask
294
+ | taskreport_sorttasks
295
+ | taskreport_sortresources
296
+ | taskreport_scenarios
297
+ | taskreport_taskroot
298
+ | taskreport_period
299
+ | taskreport_balance
300
+ | taskreport_journalmode
301
+ | taskreport_journalattributes
302
+ | taskreport_formats
303
+ | taskreport_leaftasksonly
304
+ | taskreport
305
+
306
+ taskreport_leaftasksonly: "leaftasksonly" BOOLEAN
307
+
308
+ taskreport_formats: "formats" format_list
309
+
310
+ taskreport_header: "header" (STRING | rich_text)
311
+ taskreport_footer: "footer" (STRING | rich_text)
312
+ taskreport_headline: "headline" (STRING | rich_text)
313
+ taskreport_caption: "caption" STRING
314
+ taskreport_columns: "columns" column_list
315
+ taskreport_timeformat: "timeformat" STRING
316
+ taskreport_loadunit: "loadunit" ID
317
+ taskreport_hideresource: "hideresource" (ID | FILTER_EXPR)
318
+ taskreport_hidetask: "hidetask" (ID | FILTER_EXPR)
319
+ taskreport_sorttasks: "sorttasks" sort_list
320
+ taskreport_sortresources: "sortresources" sort_list
321
+ taskreport_scenarios: "scenarios" ID ("," ID)*
322
+ taskreport_taskroot: "taskroot" TASK_PATH
323
+ taskreport_period: "period" period_spec
324
+ taskreport_balance: "balance" ID ID
325
+ taskreport_journalmode: "journalmode" ID
326
+ taskreport_journalattributes: "journalattributes" ID ("," ID)*
327
+
328
+ resourcereport: "resourcereport" ID? STRING? "{" resourcereport_body "}"
329
+ resourcereport_body: resourcereport_attr*
330
+ resourcereport_attr: resourcereport_header
331
+ | resourcereport_footer
332
+ | resourcereport_headline
333
+ | resourcereport_columns
334
+ | resourcereport_loadunit
335
+ | resourcereport_hideresource
336
+ | resourcereport_hidetask
337
+ | resourcereport_sorttasks
338
+ | resourcereport_sortresources
339
+ | resourcereport_scenarios
340
+
341
+ resourcereport_header: "header" (STRING | rich_text)
342
+ resourcereport_footer: "footer" (STRING | rich_text)
343
+ resourcereport_headline: "headline" (STRING | rich_text)
344
+ resourcereport_columns: "columns" column_list
345
+ resourcereport_loadunit: "loadunit" ID
346
+ resourcereport_hideresource: "hideresource" (ID | FILTER_EXPR)
347
+ resourcereport_hidetask: "hidetask" (ID | FILTER_EXPR)
348
+ resourcereport_sorttasks: "sorttasks" sort_list
349
+ resourcereport_sortresources: "sortresources" sort_list
350
+ resourcereport_scenarios: "scenarios" ID ("," ID)*
351
+
352
+ column_list: column_spec ("," column_spec)*
353
+ column_spec: ID column_options?
354
+ column_options: "{" column_option* "}"
355
+ column_option: "title" STRING
356
+ | "width" NUMBER
357
+ | "scale" ID
358
+ | "celltext" NUMBER STRING
359
+ | "cellcolor" cellcolor_spec
360
+ | "listtype" ID
361
+ | "listitem" STRING
362
+ | "start" (MACRO_REF | date)
363
+ | "end" (MACRO_REF | date)
364
+ | "tooltip" tooltip_spec
365
+ | MACRO_REF
366
+
367
+ cellcolor_spec: dotted_id "=" NUMBER STRING
368
+
369
+ tooltip_spec: tooltip_condition (STRING | rich_text)?
370
+ tooltip_condition: dotted_id ("(" ")")? (("!=" | "=") STRING)?
371
+ dotted_id: ID ("." ID)*
372
+
373
+ sort_list: sort_item ("," sort_item)*
374
+ sort_item: SORT_KEY
375
+ SORT_KEY: /[a-zA-Z_.]+\.(up|down)/
376
+
377
+ period_spec: PERIOD_EXPR
378
+ PERIOD_EXPR: /(%\{[^}]+\}|\d{4}-\d{2}-\d{2})(\s*\+\d+[dwmy])?/
379
+
380
+ TASK_PATH: /[a-zA-Z_][a-zA-Z0-9_.]+/
381
+
382
+ FILTER_EXPR: /@(all|none)|~[a-zA-Z_()&|=<>!0-9. -]+/
383
+
384
+ // Rich text block
385
+ rich_text: RICH_TEXT_BLOCK
386
+ RICH_TEXT_BLOCK: "-8<-" /[\s\S]*?/ "->8-"
387
+
388
+ // Extended attributes
389
+ extended_attr: ID STRING
390
+
391
+ // Macro reference
392
+ MACRO_REF: /\$\{[^}]+\}/
393
+
394
+ // Common tokens
395
+ date: DATE_TIME | DATE
396
+ DATE: /\d{4}-\d{2}-\d{2}/
397
+ DATE_TIME: /\d{4}-\d{2}-\d{2}-\d{2}:\d{2}/
398
+
399
+ ID: /[a-zA-Z_][a-zA-Z0-9_]*/
400
+ STRING: /"[^"]*"/ | /'[^']*'/
401
+ NUMBER: /-?\d+(\.\d+)?/
402
+ BOOLEAN: /true|false|yes|no|1|0/i
403
+
404
+ %import common.WS
405
+ %import common.C_COMMENT
406
+ %import common.CPP_COMMENT
407
+ %import common.SH_COMMENT
408
+
409
+ %ignore WS
410
+ %ignore C_COMMENT
411
+ %ignore CPP_COMMENT
412
+ %ignore SH_COMMENT