Skip to content
代码片段 群组 项目
提交 36e6ae23 编辑于 作者: Chad Woolley's avatar Chad Woolley
浏览文件

Implement glfm_example_status.yml

- Implement support and add documentation
  for example_status logic.
- See
  https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#glfm_example_statusyml
上级 5ca923a7
No related branches found
No related tags found
无相关合并请求
......@@ -213,32 +213,42 @@ HTML which differs from the canonical HTML examples from the specification.
For every Markdown example in the GLFM specification, three
versions of HTML can potentially be rendered from the example:
1. **Static HTML**: HTML produced by the backend (Ruby) renderer, which
contains extra styling and behavioral HTML. For example, **Create task** buttons
added for dynamically creating an issue from a task list item.
The GitLab [Markdown API](../../../api/markdown.md) generates HTML
for a given Markdown string using this method.
1. **WYSIWYG HTML**: HTML produced by the frontend (JavaScript) Content Editor,
which includes parsing and rendering logic. Used to present an editable document
in the ProseMirror WYSIWYG editor.
1. **Canonical HTML**: The clean, basic version of HTML rendered from Markdown.
1. For the examples which come from the CommonMark specification and
GFM extensions specification,
the canonical HTML is the exact identical HTML found in the
GFM
`spec.txt` example blocks.
1. For GLFM extensions to the <abbr title="GitHub Flavored Markdown">GFM</abbr> / CommonMark
specification, a `glfm_canonical_examples.txt`
[input specification file](#input-specification-files) contains the
Markdown examples and corresponding canonical HTML examples.
- Static HTML.
- WYSIWYG HTML.
- Canonical HTML.
#### Static HTML
**Static HTML** is HTML produced by the backend (Ruby) renderer, which
contains extra styling and behavioral HTML. For example, **Create task** buttons
added for dynamically creating an issue from a task list item.
The GitLab [Markdown API](../../../api/markdown.md) generates HTML
for a given Markdown string using this method.
#### WYSIWYG HTML
**WYSIWYG HTML** is HTML produced by the frontend (JavaScript) Content Editor,
which includes parsing and rendering logic. It is used to present an editable document
in the ProseMirror WYSIWYG editor.
#### Canonical HTML
**Canonical HTML** is the clean, basic version of HTML rendered from Markdown.
1. For the examples which come from the CommonMark specification and
GFM extensions specification, the canonical HTML is the exact identical HTML found in the
GFM `spec.txt` example blocks.
1. For GLFM extensions to the <abbr title="GitHub Flavored Markdown">GFM</abbr> / CommonMark
specification, a `glfm_canonical_examples.txt` [input specification file](#input-specification-files)
contains the Markdown examples and corresponding canonical HTML examples.
As the rendered static and WYSIWYG HTML from the backend (Ruby) and frontend (JavaScript)
renderers contain extra HTML, their rendered HTML can be converted to canonical HTML
by a [canonicalization](#canonicalization-of-html) process.
### Canonicalization of HTML
#### Canonicalization of HTML
The rendered [static HTML](#static-html) and [WYSIWYG HTML](#wysiwyg-html)
from the backend (Ruby) and frontend (JavaScript) renderers usually contains extra styling
or HTML elements, to support specific appearance and behavioral requirements.
Neither the backend (Ruby) nor the frontend (JavaScript) rendered can directly render canonical HTML.
Neither the backend nor the frontend rendering logic can directly render the clean, basic canonical HTML.
Nor should they be able to, because:
- It's not a direct requirement to support any GitLab application feature.
......@@ -578,23 +588,57 @@ The actual file should not have these prefixed `|` characters.
controls the behavior of the [scripts](#scripts) and [tests](#types-of-markdown-tests-driven-by-the-glfm-specification).
- It is manually updated.
- It controls the status of automatic generation of files based on Markdown examples.
- It allows example snapshot generation, Markdown conformance tests, or
Markdown snapshot tests to be skipped for individual examples. For example, if
they are unimplemented, broken, or cannot be tested for some reason.
- The `skip_update_example_snapshot*` fields control the status of automatic generation of
snapshot example entries based on Markdown examples.
- The `skip_running_*` control allow Markdown conformance tests or
Markdown snapshot tests to be skipped for individual examples.
- This allows control over skipping this processing or testing of various examples when they
are unimplemented, partially implemented, broken, or cannot be generated or tested for some reason.
- All entries default to false. They can be set to true by specifying a Ruby
value which evaluates as truthy. This could be the boolean `true` value, but ideally should
be a string describing why the example's updating or testing is being skipped.
- When a `skip_update_example_snapshot*` entry is true, the existing value is preserved.
However, since the YAML is re-written, the style of the string value and its
[Block Chomping Indicator (`|`)](https://yaml.org/spec/1.2.2/#8112-block-chomping-indicator)
may be modified, because the Ruby `psych` YAML library automatically determines this.
The following optional entries are supported for each example. They all default to `false`:
- `skip_update_example_snapshots`: When true, skips any addition or update of any this example's entries
in the [`spec/fixtures/glfm/example_snapshots/html.yml`](#specfixturesglfmexample_snapshotshtmlyml) file
or the [`spec/fixtures/glfm/example_snapshots/prosemirror_json.yml`](#specfixturesglfmexample_snapshotsprosemirror_jsonyml) file.
If this value is truthy, then no other `skip_update_example_snapshot_*` entries can be truthy,
and an error is raised if any of them are.
- `skip_update_example_snapshot_html_static`: When true, skips addition or update of this example's [static HTML](#static-html)
entry in the [`spec/fixtures/glfm/example_snapshots/html.yml`](#specfixturesglfmexample_snapshotshtmlyml) file.
- `skip_update_example_snapshot_html_wysiwyg`: When true, skips addition or update of this example's [WYSIWYG HTML](#wysiwyg-html)
entry in the [`spec/fixtures/glfm/example_snapshots/html.yml`](#specfixturesglfmexample_snapshotshtmlyml) file.
- `skip_update_example_snapshot_prosemirror_json`: When true, skips addition or update of this example's
entry in the [`spec/fixtures/glfm/example_snapshots/prosemirror_json.yml`](#specfixturesglfmexample_snapshotsprosemirror_jsonyml) file.
- `skip_running_conformance_static_tests`: When true, skips running the [Markdown conformance tests](#markdown-conformance-testing)
of the [static HTML](#static-html) for this example.
- `skip_running_conformance_wysiwyg_tests`: When true, skips running the [Markdown conformance tests](#markdown-conformance-testing)
of the [WYSIWYG HTML](#wysiwyg-html) for this example.
- `skip_running_snapshot_static_html_tests`: When true, skips running the [Markdown snapshot tests](#markdown-snapshot-testing)
of the [static HTML](#multiple-versions-of-rendered-html) for this example.
- `skip_running_snapshot_wysiwyg_html_tests`: When true, skips running the [Markdown snapshot tests](#markdown-snapshot-testing)
of the [WYSIWYG HTML](#wysiwyg-html) for this example.
- `skip_running_snapshot_prosemirror_json_tests`: When true, skips running the [Markdown snapshot tests](#markdown-snapshot-testing)
of the [ProseMirror JSON](#specfixturesglfmexample_snapshotsprosemirror_jsonyml) for this example.
`glfm_specification/input/gitlab_flavored_markdown/glfm_example_status.yml` sample entry:
```yaml
07_99_an_example_with_incomplete_wysiwyg_implementation_1:
skip_update_example_snapshots: false
skip_update_example_snapshot_html_static: false
skip_update_example_snapshot_html_wysiwyg: false
skip_running_conformance_static_tests: false
skip_running_conformance_wysiwyg_tests: false
skip_running_snapshot_static_html_tests: false
skip_running_snapshot_wysiwyg_html_tests: false
skip_running_snapshot_prosemirror_json_tests: false
skip_update_example_snapshots: 'An explanation of the reason for skipping.'
skip_update_example_snapshot_html_static: 'An explanation of the reason for skipping.'
skip_update_example_snapshot_html_wysiwyg: 'An explanation of the reason for skipping.'
skip_update_example_snapshot_prosemirror_json: 'An explanation of the reason for skipping.'
skip_running_conformance_static_tests: 'An explanation of the reason for skipping.'
skip_running_conformance_wysiwyg_tests: 'An explanation of the reason for skipping.'
skip_running_snapshot_static_html_tests: 'An explanation of the reason for skipping.'
skip_running_snapshot_wysiwyg_html_tests: 'An explanation of the reason for skipping.'
skip_running_snapshot_prosemirror_json_tests: 'An explanation of the reason for skipping.'
```
#### Output specification files
......
---
- 06_05__inlines__emphasis_and_strong_emphasis__37:
skip_update_example_snapshots: 'Psych YAML library has a problem with dumping "5__6__78", it thinks its an invalid integer'
- 07_01_first_gitlab_specific_section_with_examples_strong_but_with_two_asterisks:
02_01__preliminaries__tabs__001:
# NOTE: False values are optional, they are only included here for reference. See
# https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#glfm_example_statusyml
# for more details.
skip_update_example_snapshots: false
skip_running_snapshot_static_html_tests: false
skip_running_snapshot_wysiwyg_html_tests: false
skip_running_snapshot_prosemirror_json_tests: false
skip_running_conformance_static_tests: false
skip_running_conformance_wysiwyg_tests: false
- 07_02_first_gitlab_specific_section_with_examples_strong_but_with_html:
skip_update_example_snapshots: false
skip_running_snapshot_static_html_tests: false
skip_running_snapshot_wysiwyg_html_tests: false
skip_running_snapshot_prosemirror_json_tests: false
skip_running_conformance_static_tests: false
skip_running_conformance_wysiwyg_tests: false
skip_update_example_snapshot_html_static: false
skip_update_example_snapshot_html_wysiwyg: false
skip_update_example_snapshot_prosemirror_json: false
skip_running_conformance_static_tests: false # NOT YET SUPPORTED
skip_running_conformance_wysiwyg_tests: false # NOT YET SUPPORTED
skip_running_snapshot_static_html_tests: false # NOT YET SUPPORTED
skip_running_snapshot_wysiwyg_html_tests: false # NOT YET SUPPORTED
skip_running_snapshot_prosemirror_json_tests: false # NOT YET SUPPORTED
......@@ -13,6 +13,10 @@
# for details on the implementation and usage of this script. This developers guide
# contains diagrams and documentation of this script,
# including explanations and examples of all files it reads and writes.
#
# Also note that this script is intentionally written in a pure-functional (not OO) style,
# with no dependencies on Rails or the GitLab libraries. These choices are intended to make
# it faster and easier to test and debug.
module Glfm
class UpdateExampleSnapshots
include Constants
......@@ -27,28 +31,20 @@ def process(skip_static_and_wysiwyg: false)
output('(Skipping static HTML generation)') if skip_static_and_wysiwyg
glfm_spec_txt_lines, _glfm_examples_status_lines = read_input_files
output("Reading #{GLFM_SPEC_TXT_PATH}...")
glfm_spec_txt_lines = File.open(GLFM_SPEC_TXT_PATH).readlines
# Parse all the examples from `spec.txt`, using a Ruby port of the Python `get_tests`
# function the from original CommonMark/GFM `spec_test.py` script.
all_examples = parse_examples(glfm_spec_txt_lines)
add_example_names(all_examples)
write_snapshot_example_files(all_examples, skip_static_and_wysiwyg: skip_static_and_wysiwyg)
end
private
def read_input_files
[
GLFM_SPEC_TXT_PATH,
GLFM_EXAMPLE_STATUS_YML_PATH
].map do |path|
output("Reading #{path}...")
File.open(path).readlines
end
end
def add_example_names(all_examples)
# NOTE: This method assumes:
# 1. Section 2 is the first section which contains examples
......@@ -83,7 +79,7 @@ def add_example_names(all_examples)
formatted_headers_text = headers.join('__').tr('-', '_').tr(' ', '_').downcase
hierarchy_level = "#{h1_count.to_s.rjust(2, '0')}_#{h2_count.to_s.rjust(2, '0')}"
position_within_section = index_within_h2.to_s.rjust(2, '0')
position_within_section = index_within_h2.to_s.rjust(3, '0')
name = "#{hierarchy_level}__#{formatted_headers_text}__#{position_within_section}"
converted_name = name.tr('(', '').tr(')', '') # remove any parens from the name
example[:name] = converted_name
......@@ -91,6 +87,10 @@ def add_example_names(all_examples)
end
def write_snapshot_example_files(all_examples, skip_static_and_wysiwyg:)
output("Reading #{GLFM_EXAMPLE_STATUS_YML_PATH}...")
glfm_examples_statuses = YAML.safe_load(File.open(GLFM_EXAMPLE_STATUS_YML_PATH))
validate_glfm_example_status_yml(glfm_examples_statuses)
write_examples_index_yml(all_examples)
write_markdown_yml(all_examples)
......@@ -104,9 +104,20 @@ def write_snapshot_example_files(all_examples, skip_static_and_wysiwyg:)
static_html_hash = generate_static_html(markdown_yml_tempfile_path)
wysiwyg_html_and_json_hash = generate_wysiwyg_html_and_json(markdown_yml_tempfile_path)
write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash)
write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash, glfm_examples_statuses)
write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash, glfm_examples_statuses)
end
def validate_glfm_example_status_yml(glfm_examples_statuses)
glfm_examples_statuses.each do |example_name, statuses|
next unless statuses &&
statuses['skip_update_example_snapshots'] &&
statuses.any? { |key, value| key.include?('skip_update_example_snapshot_') && !!value }
write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash)
raise "Error: '#{example_name}' must not have any 'skip_update_example_snapshot_*' values specified " \
"if 'skip_update_example_snapshots' is truthy"
end
end
def write_examples_index_yml(all_examples)
......@@ -186,27 +197,77 @@ def generate_wysiwyg_html_and_json(markdown_yml_tempfile_path)
YAML.load_file(wysiwyg_html_and_json_tempfile_path)
end
def write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash)
generate_and_write_for_all_examples(all_examples, ES_HTML_YML_PATH) do |example, hash|
hash[example.fetch(:name)] = {
def write_html_yml(all_examples, static_html_hash, wysiwyg_html_and_json_hash, glfm_examples_statuses)
generate_and_write_for_all_examples(
all_examples, ES_HTML_YML_PATH, glfm_examples_statuses
) do |example, hash, existing_hash|
name = example.fetch(:name)
example_statuses = glfm_examples_statuses[name] || {}
static = if example_statuses['skip_update_example_snapshot_html_static']
existing_hash.dig(name, 'static')
else
static_html_hash[name]
end
wysiwyg = if example_statuses['skip_update_example_snapshot_html_wysiwyg']
existing_hash.dig(name, 'wysiwyg')
else
wysiwyg_html_and_json_hash.dig(name, 'html')
end
hash[name] = {
'canonical' => example.fetch(:html),
'static' => static_html_hash.fetch(example.fetch(:name)),
'wysiwyg' => wysiwyg_html_and_json_hash.fetch(example.fetch(:name)).fetch('html')
}
'static' => static,
'wysiwyg' => wysiwyg
}.compact # Do not assign nil values
end
end
def write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash)
generate_and_write_for_all_examples(all_examples, ES_PROSEMIRROR_JSON_YML_PATH) do |example, hash|
hash[example.fetch(:name)] = wysiwyg_html_and_json_hash.fetch(example.fetch(:name)).fetch('json')
def write_prosemirror_json_yml(all_examples, wysiwyg_html_and_json_hash, glfm_examples_statuses)
generate_and_write_for_all_examples(
all_examples, ES_PROSEMIRROR_JSON_YML_PATH, glfm_examples_statuses
) do |example, hash, existing_hash|
name = example.fetch(:name)
json = if glfm_examples_statuses.dig(name, 'skip_update_example_snapshot_prosemirror_json')
existing_hash.dig(name)
else
wysiwyg_html_and_json_hash.dig(name, 'json')
end
# Do not assign nil values
hash[name] = json if json
end
end
def generate_and_write_for_all_examples(all_examples, output_file_path, literal_scalars: true, &generator_block)
output("Writing #{output_file_path}...")
generated_examples_hash = all_examples.each_with_object({}, &generator_block)
def generate_and_write_for_all_examples(
all_examples, output_file_path, glfm_examples_statuses = {}, literal_scalars: true
)
preserve_existing = !glfm_examples_statuses.empty?
output("#{preserve_existing ? 'Creating/Updating' : 'Creating/Overwriting'} #{output_file_path}...")
existing_hash = preserve_existing ? YAML.safe_load(File.open(output_file_path)) : {}
output_hash = all_examples.each_with_object({}) do |example, hash|
name = example.fetch(:name)
if (reason = glfm_examples_statuses.dig(name, 'skip_update_example_snapshots'))
# Output the reason for skipping the example, but only once, not multiple times for each file
output("Skipping '#{name}'. Reason: #{reason}") unless glfm_examples_statuses.dig(name, :already_printed)
# We just store the `:already_printed` flag in the hash entry itself. Then we
# don't need an instance variable to keep the state, and this can remain a pure function ;)
glfm_examples_statuses[name][:already_printed] = true
# Copy over the existing example only if it exists and preserve_existing is true, otherwise omit this example
# noinspection RubyScope
hash[name] = existing_hash[name] if existing_hash[name]
next
end
yield(example, hash, existing_hash)
end
yaml_string = dump_yaml_with_formatting(generated_examples_hash, literal_scalars: literal_scalars)
yaml_string = dump_yaml_with_formatting(output_hash, literal_scalars: literal_scalars)
write_file(output_file_path, yaml_string)
end
......
此差异已折叠。
此差异已折叠。
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册