Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
S
Scripts
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Specification Tools
Scripts
Commits
98b64bd6
Commit
98b64bd6
authored
3 months ago
by
Andreas Kraft
Browse files
Options
Downloads
Patches
Plain Diff
New script to process markdown includes and front matter. Might be extended.
parent
a1b466b0
Branches
Branches containing commit
No related tags found
1 merge request
!1
Restructuring and cleaning scripts for Mkdocs
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
processMDSpec.py
+198
-0
198 additions, 0 deletions
processMDSpec.py
with
198 additions
and
0 deletions
processMDSpec.py
0 → 100644
+
198
−
0
View file @
98b64bd6
#
# processMDSpec.py
#
# (c) 2025 by Andreas Kraft
# License: BSD 3-Clause License. See the LICENSE file for further details.
#
"""
This script processes markdown specification files. It handles the
include statements and the front matter. It can also render the markdown
content on console or output the front matter only.
"""
from
__future__
import
annotations
from
typing
import
Tuple
,
Generator
import
argparse
from
rich
import
print
,
markdown
import
re
,
sys
,
yaml
from
contextlib
import
contextmanager
_frontMatter
:
dict
=
{}
_includeStack
:
list
[
str
]
=
[]
@contextmanager
def
includeStack
(
filename
:
str
)
->
Generator
[
None
,
None
,
None
]:
"""
Handle the include stack.
This is used to detect circular includes and to keep track of the
include stack.
Args:
filename: The name of the file being processed.
Raises:
Exception: If a circular include is detected.
Returns:
Generator: A generator that yields nothing.
"""
if
filename
in
_includeStack
:
print
(
f
'
[red]Circular include detected:
{
filename
}
'
)
raise
Exception
(
'
Circular include detected
'
)
_includeStack
.
append
(
filename
)
yield
_includeStack
.
pop
()
def
processFrontMatter
(
lines
:
list
[
str
],
args
:
argparse
.
Namespace
)
->
Tuple
[
dict
,
list
[
str
]]:
"""
Process the front matter of a markdown file. This includes extracting
the front matter information and returning it as a dictionary.
Currently only YAML front matter is supported. It can be extended later.
Args:
lines: The lines of the markdown file.
args: The command line arguments.
Raises:
yaml.YAMLError: If the front matter cannot be parsed as YAML.
Returns:
dict: The front matter information as a dictionary.
list[str]: The lines of the markdown file without the front matter.
"""
if
not
lines
or
not
lines
[
0
].
startswith
(
'
---
'
):
return
{},
lines
frontMatterLines
:
list
[
str
]
=
[]
for
line
in
lines
[
1
:]:
if
re
.
match
(
r
'
^---\s*
'
,
line
):
break
frontMatterLines
.
append
(
line
)
# Remove the front matter from the lines
lines
=
lines
[
len
(
frontMatterLines
)
+
2
:]
# Parse the front matter as YAML
try
:
return
yaml
.
safe_load
(
''
.
join
(
frontMatterLines
)),
lines
except
yaml
.
YAMLError
as
e
:
print
(
f
'
[red]Error parsing front matter:
{
e
}
'
)
raise
def
processFile
(
args
:
argparse
.
Namespace
)
->
str
:
"""
Handle the include statements in the markdown files. This includes
processing the include statements and removing the include statements
from the markdown files.
Args:
args: The command line arguments.
Raises:
Exception: If the file cannot be processed.
Returns:
The processed markdown content as a string.
"""
def
handleIncludesForFile
(
filename
:
str
)
->
str
:
"""
Read a single markdown file and return its content.
Args:
filename: The name of the file to read.
Raises:
FileNotFoundError: If the file cannot be found.
Returns:
The content of the file.
"""
with
includeStack
(
filename
):
try
:
with
open
(
filename
,
'
r
'
)
as
f
:
lines
=
f
.
readlines
()
except
FileNotFoundError
:
print
(
f
'
[red]File not found:
{
filename
}
'
)
raise
# extract front matter information
fm
,
lines
=
processFrontMatter
(
lines
,
args
)
if
fm
:
_frontMatter
[
filename
]
=
fm
if
not
args
.
doInclude
:
return
''
.
join
(
lines
)
inCodeFence
=
False
for
line
in
lines
:
# Ignore code fences
if
re
.
match
(
r
'
^\s*```.*
'
,
line
):
inCodeFence
=
not
inCodeFence
continue
if
inCodeFence
:
continue
# Check for ::include{file=...} pattern using regex at the beginning of a line
match
=
re
.
search
(
r
'
^::include\{\s*file=(.*?)\s*\}
'
,
line
.
strip
())
if
match
:
include_filename
=
match
.
group
(
1
)
# Read the included file and replace the include statement with its content
include_content
=
handleIncludesForFile
(
include_filename
)
lines
[
lines
.
index
(
line
)]
=
include_content
return
''
.
join
(
lines
)
return
handleIncludesForFile
(
args
.
document
)
if
__name__
==
'
__main__
'
:
parser
=
argparse
.
ArgumentParser
(
description
=
'
Process markdown specification files.
'
)
parser
.
add_argument
(
'
--no-include
'
,
dest
=
'
doInclude
'
,
action
=
'
store_false
'
,
default
=
True
,
help
=
"
don
'
t process include statements
"
)
parser
.
add_argument
(
'
--render-markdown
'
,
'
-md
'
,
dest
=
'
renderAsMarkdown
'
,
action
=
'
store_true
'
,
help
=
'
render output as markdown
'
)
parser
.
add_argument
(
'
--process-frontmatter
'
,
'
-fm
'
,
dest
=
'
outputFrontMatter
'
,
action
=
'
store_true
'
,
help
=
'
output front matter only
'
)
parser
.
add_argument
(
'
--frontmatter-only
'
,
'
-fmo
'
,
dest
=
'
onlyFrontMatter
'
,
action
=
'
store_true
'
,
help
=
'
output only front matter
'
)
parser
.
add_argument
(
'
--verbose
'
,
'
-v
'
,
action
=
'
store_true
'
,
help
=
'
print debug information to stderr.
'
)
parser
.
add_argument
(
'
document
'
,
type
=
str
,
help
=
'
a markdown specification document to process
'
)
args
=
parser
.
parse_args
()
if
args
.
verbose
:
if
not
args
.
doInclude
:
print
(
f
'
[yellow]Skipping processing include statements
'
,
file
=
sys
.
stderr
)
else
:
print
(
f
'
[green]Processing include statements
'
,
file
=
sys
.
stderr
)
try
:
lines
=
processFile
(
args
)
except
Exception
as
e
:
print
(
f
'
[red]Error processing file:
{
e
}
'
,
file
=
sys
.
stderr
)
quit
(
1
)
if
args
.
outputFrontMatter
or
args
.
onlyFrontMatter
:
# Collect front matter information in the output
if
not
args
.
onlyFrontMatter
:
print
(
'
---
'
)
# The following is a workaround to keep the order of the dictionary
# see https://stackoverflow.com/a/52621703
yaml
.
add_representer
(
dict
,
lambda
self
,
data
:
yaml
.
representer
.
SafeRepresenter
.
represent_dict
(
self
,
data
.
items
()))
print
(
yaml
.
dump
(
_frontMatter
,
default_flow_style
=
False
),
end
=
''
)
if
not
args
.
onlyFrontMatter
:
print
(
'
---
'
)
if
not
args
.
onlyFrontMatter
:
if
args
.
renderAsMarkdown
:
# Render the markdown content
print
(
markdown
.
Markdown
(
lines
))
else
:
# Print the raw markdown content
print
(
lines
)
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment