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.
- scriptplan/__init__.py +22 -0
- scriptplan/cli/__init__.py +7 -0
- scriptplan/cli/main.py +546 -0
- scriptplan/core/__init__.py +0 -0
- scriptplan/core/account.py +125 -0
- scriptplan/core/allocation.py +69 -0
- scriptplan/core/booking.py +39 -0
- scriptplan/core/journal.py +377 -0
- scriptplan/core/leave.py +14 -0
- scriptplan/core/limits.py +354 -0
- scriptplan/core/project.py +924 -0
- scriptplan/core/property.py +1290 -0
- scriptplan/core/resource.py +198 -0
- scriptplan/core/resource_scenario.py +711 -0
- scriptplan/core/scenario.py +5 -0
- scriptplan/core/scenario_data.py +39 -0
- scriptplan/core/shift.py +71 -0
- scriptplan/core/task.py +77 -0
- scriptplan/core/task_scenario.py +1515 -0
- scriptplan/core/timesheet.py +457 -0
- scriptplan/core/working_hours.py +231 -0
- scriptplan/parser/__init__.py +0 -0
- scriptplan/parser/macro_processor.py +264 -0
- scriptplan/parser/tjp.lark +412 -0
- scriptplan/parser/tjp_parser.py +1904 -0
- scriptplan/py.typed +0 -0
- scriptplan/report/__init__.py +75 -0
- scriptplan/report/html_generator.py +477 -0
- scriptplan/report/report.py +466 -0
- scriptplan/report/report_base.py +397 -0
- scriptplan/report/report_context.py +248 -0
- scriptplan/report/resource_report.py +341 -0
- scriptplan/report/table_report.py +693 -0
- scriptplan/report/task_report.py +362 -0
- scriptplan/report/text_report.py +172 -0
- scriptplan/scheduler/__init__.py +0 -0
- scriptplan/scheduler/batch_processor.py +238 -0
- scriptplan/scheduler/scoreboard.py +120 -0
- scriptplan/utils/__init__.py +0 -0
- scriptplan/utils/data_cache.py +46 -0
- scriptplan/utils/logger.py +243 -0
- scriptplan/utils/message_handler.py +515 -0
- scriptplan/utils/time.py +195 -0
- scriptplan-0.9.0.dist-info/METADATA +161 -0
- scriptplan-0.9.0.dist-info/RECORD +49 -0
- scriptplan-0.9.0.dist-info/WHEEL +5 -0
- scriptplan-0.9.0.dist-info/entry_points.txt +2 -0
- scriptplan-0.9.0.dist-info/licenses/LICENSE +201 -0
- 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
|