Skip to content

Commit ab227b3

Browse files
authored
[1.x] Consolidate field-details doc template (#897) (#946)
1 parent 64ea560 commit ab227b3

13 files changed

+327
-251
lines changed

CHANGELOG.next.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ Thanks, you're awesome :-) -->
104104
* Jinja2 templates now define the doc structure for the AsciiDoc generator. #865
105105
* Intermediate `ecs_flat.yml` and `ecs_nested.yml` files are now generated for each individual subset,
106106
in addition to the intermediate files generated for the combined subset. #873
107+
* Field details Jinja2 template components have been consolidated into one template #897
107108

108109
#### Deprecated
109110

docs/field-details.asciidoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
[[ecs-base]]
32
=== Base Fields
43

@@ -7085,3 +7084,4 @@ Note also that the `x509` fields are not expected to be used directly at the roo
70857084

70867085

70877086

7087+

scripts/generators/asciidoc_fields.py

Lines changed: 80 additions & 190 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@
55

66
from generators import ecs_helpers
77

8-
# jinja2 setup
9-
TEMPLATE_DIR = path.join(path.dirname(path.abspath(__file__)), '../templates')
10-
template_loader = jinja2.FileSystemLoader(searchpath=TEMPLATE_DIR)
11-
template_env = jinja2.Environment(loader=template_loader)
12-
138

149
def generate(nested, ecs_version, out_dir):
1510
save_asciidoc(path.join(out_dir, 'fields.asciidoc'), page_field_index(nested, ecs_version))
@@ -19,6 +14,64 @@ def generate(nested, ecs_version, out_dir):
1914
# Helpers
2015

2116

17+
def render_fieldset_reuse_text(fieldset):
18+
"""Renders the expected nesting locations
19+
if the the `reusable` object is present.
20+
21+
:param fieldset: The fieldset to evaluate
22+
"""
23+
if not fieldset.get('reusable'):
24+
return None
25+
reusable_fields = fieldset['reusable']['expected']
26+
sorted_fields = sorted(reusable_fields, key=lambda k: k['full'])
27+
return map(lambda f: f['full'], sorted_fields)
28+
29+
30+
def render_nestings_reuse_section(fieldset):
31+
"""Renders the reuse section entries.
32+
33+
:param fieldset: The target fieldset
34+
"""
35+
if not fieldset.get('reused_here'):
36+
return None
37+
rows = []
38+
for reused_here_entry in fieldset['reused_here']:
39+
rows.append({
40+
'flat_nesting': "{}.*".format(reused_here_entry['full']),
41+
'name': reused_here_entry['schema_name'],
42+
'short': reused_here_entry['short']
43+
})
44+
45+
return sorted(rows, key=lambda x: x['flat_nesting'])
46+
47+
48+
def extract_allowed_values_key_names(field):
49+
"""Extracts the `name` keys from the field's
50+
allowed_values if present in the field
51+
object.
52+
53+
:param field: The target field
54+
"""
55+
if not field.get('allowed_values'):
56+
return []
57+
return ecs_helpers.list_extract_keys(field['allowed_values'], 'name')
58+
59+
60+
def sort_fields(fieldset):
61+
"""Prepares a fieldset's fields for being
62+
passed into the j2 template for rendering. This
63+
includes sorting them into a list of objects and
64+
adding a field for the names of any allowed values
65+
for the field, if present.
66+
67+
:param fieldset: The target fieldset
68+
"""
69+
fields_list = list(fieldset['fields'].values())
70+
for field in fields_list:
71+
field['allowed_value_names'] = extract_allowed_values_key_names(field)
72+
return sorted(fields_list, key=lambda field: field['name'])
73+
74+
2275
def templated(template_name):
2376
"""Decorator function to simplify rendering a template.
2477
@@ -53,210 +106,47 @@ def save_asciidoc(f, text):
53106
with open(f, "w") as outfile:
54107
outfile.write(text)
55108

109+
# jinja2 setup
110+
111+
112+
TEMPLATE_DIR = path.join(path.dirname(path.abspath(__file__)), '../templates')
113+
template_loader = jinja2.FileSystemLoader(searchpath=TEMPLATE_DIR)
114+
template_env = jinja2.Environment(loader=template_loader)
56115

57116
# Rendering schemas
58117

59118
# Field Index
60119

61-
@templated('fields_template.j2')
120+
121+
@templated('fields.j2')
62122
def page_field_index(nested, ecs_version):
63123
fieldsets = ecs_helpers.dict_sorted_by_keys(nested, ['group', 'name'])
64124
return dict(ecs_version=ecs_version, fieldsets=fieldsets)
65125

66126

67127
# Field Details Page
68128

69-
70129
def page_field_details(nested):
71-
page_text = ''
72-
for fieldset in ecs_helpers.dict_sorted_by_keys(nested, ['group', 'name']):
73-
page_text += render_fieldset(fieldset, nested)
74-
return page_text
75-
76-
77-
def render_fieldset(fieldset, nested):
78-
text = field_details_table_header(
79-
title=fieldset['title'],
80-
name=fieldset['name'],
81-
description=fieldset['description']
82-
)
83-
84-
text += render_fields(fieldset['fields'])
85-
86-
text += table_footer()
87-
88-
text += render_fieldset_reuse_section(fieldset, nested)
89-
90-
return text
91-
92-
93-
def render_fields(fields):
94-
text = ''
95-
for _, field in sorted(fields.items()):
96-
# Skip fields nested in this field set
97-
if 'original_fieldset' not in field:
98-
text += render_field_details_row(field)
99-
return text
100-
101-
102-
def render_field_allowed_values(field):
103-
if not 'allowed_values' in field:
104-
return ''
105-
allowed_values = ', '.join(ecs_helpers.list_extract_keys(field['allowed_values'], 'name'))
106-
107-
return field_acceptable_value_names(
108-
allowed_values=allowed_values,
109-
flat_name=field['flat_name'],
110-
dashed_name=field['dashed_name']
111-
)
112-
113-
114-
def render_field_details_row(field):
115-
example = ''
116-
if 'allowed_values' in field:
117-
example = render_field_allowed_values(field)
118-
elif 'example' in field:
119-
example = "example: `{}`".format(str(field['example']))
120-
121-
field_type_with_mf = field['type']
122-
if 'multi_fields' in field:
123-
field_type_with_mf += "\n\nMulti-fields:\n\n"
124-
for mf in field['multi_fields']:
125-
field_type_with_mf += "* {} (type: {})\n\n".format(mf['flat_name'], mf['type'])
126-
127-
field_normalization = ''
128-
if 'array' in field['normalize']:
129-
field_normalization = "\nNote: this field should contain an array of values.\n\n"
130-
131-
text = field_details_row(
132-
flat_name=field['flat_name'],
133-
description=field['description'],
134-
field_type=field_type_with_mf,
135-
example=example,
136-
normalization=field_normalization,
137-
level=field['level']
138-
)
139-
140-
return text
141-
142-
143-
def render_fieldset_reuse_section(fieldset, nested):
144-
'''Render the section on where field set can be nested, and which field sets can be nested here'''
145-
if not ('nestings' in fieldset or 'reusable' in fieldset):
146-
return ''
147-
148-
text = field_reuse_section(
149-
reuse_of_fieldset=render_fieldset_reuses_text(fieldset)
150-
)
151-
152-
if 'nestings' in fieldset:
153-
text += nestings_table_header(
154-
name=fieldset['name'],
155-
title=fieldset['title']
156-
)
157-
rows = []
158-
for reused_here_entry in fieldset['reused_here']:
159-
rows.append({
160-
'flat_nesting': "{}.*".format(reused_here_entry['full']),
161-
'name': reused_here_entry['schema_name'],
162-
'short': reused_here_entry['short']
163-
})
164-
165-
for row in sorted(rows, key=lambda x: x['flat_nesting']):
166-
text += nestings_row(
167-
nesting_name=row['name'],
168-
flat_nesting=row['flat_nesting'],
169-
nesting_short=row['short']
170-
)
171-
172-
text += table_footer()
173-
return text
174-
175-
176-
def render_fieldset_reuses_text(fieldset):
177-
'''Render where a given field set is expected to be reused'''
178-
if 'reusable' not in fieldset:
179-
return ''
180-
181-
section_name = fieldset['name']
182-
sorted_fields = sorted(fieldset['reusable']['expected'], key=lambda k: k['full'])
183-
rendered_fields = map(lambda f: "`{}`".format(f['full']), sorted_fields)
184-
text = "The `{}` fields are expected to be nested at: {}.\n\n".format(
185-
section_name, ', '.join(rendered_fields))
186-
187-
if 'top_level' in fieldset['reusable'] and fieldset['reusable']['top_level']:
188-
template = "Note also that the `{}` fields may be used directly at the root of the events.\n\n"
189-
else:
190-
template = "Note also that the `{}` fields are not expected to " + \
191-
"be used directly at the root of the events.\n\n"
192-
text += template.format(section_name)
193-
return text
194-
195-
196-
# Templates
197-
198-
def table_footer():
199-
return '''
200-
|=====
201-
'''
202-
203-
# Field Details Page
204-
205-
# Main Fields Table
206-
207-
208-
@templated('field_details/table_header.j2')
209-
def field_details_table_header(title, name, description):
210-
return dict(name=name, title=title, description=description)
211-
212-
213-
@templated('field_details/row.j2')
214-
def field_details_row(flat_name, description, field_type, normalization, example, level):
215-
return dict(
216-
flat_name=flat_name,
217-
description=description,
218-
field_type=field_type,
219-
normalization=normalization,
220-
example=example,
221-
level=level
222-
)
223-
224-
225-
@templated('field_details/acceptable_value_names.j2')
226-
def field_acceptable_value_names(allowed_values, dashed_name, flat_name):
227-
return dict(
228-
allowed_values=allowed_values,
229-
dashed_name=dashed_name,
230-
flat_name=flat_name
231-
)
232-
233-
234-
# Field reuse
235-
236-
@templated('field_details/field_reuse_section.j2')
237-
def field_reuse_section(reuse_of_fieldset):
238-
return dict(reuse_of_fieldset=reuse_of_fieldset)
239-
240-
241-
# Nestings table
242-
243-
@templated('field_details/nestings_table_header.j2')
244-
def nestings_table_header(name, title):
245-
return dict(name=name, title=title)
130+
fieldsets = ecs_helpers.dict_sorted_by_keys(nested, ['group', 'name'])
131+
results = (generate_field_details_page(fieldset) for fieldset in fieldsets)
132+
return ''.join(results)
246133

247134

248-
@templated('field_details/nestings_row.j2')
249-
def nestings_row(nesting_name, flat_nesting, nesting_short):
250-
return dict(
251-
nesting_name=nesting_name,
252-
flat_nesting=flat_nesting,
253-
nesting_short=nesting_short
254-
)
135+
@templated('field_details.j2')
136+
def generate_field_details_page(fieldset):
137+
# render field reuse text section
138+
sorted_reuse_fields = render_fieldset_reuse_text(fieldset)
139+
render_nestings_reuse_fields = render_nestings_reuse_section(fieldset)
140+
sorted_fields = sort_fields(fieldset)
141+
return dict(fieldset=fieldset,
142+
sorted_reuse_fields=sorted_reuse_fields,
143+
render_nestings_reuse_section=render_nestings_reuse_fields,
144+
sorted_fields=sorted_fields)
255145

256146

257147
# Allowed values section
258148

259-
@templated('field_values_template.j2')
149+
@templated('field_values.j2')
260150
def page_field_values(nested, template_name='field_values_template.j2'):
261151
category_fields = ['event.kind', 'event.category', 'event.type', 'event.outcome']
262152
nested_fields = []

0 commit comments

Comments
 (0)