Skip to content

Commit

Permalink
Added syntax help to minijinja-cli (#607)
Browse files Browse the repository at this point in the history
  • Loading branch information
mitsuhiko authored Oct 25, 2024
1 parent bda02e9 commit 4d12ba5
Show file tree
Hide file tree
Showing 8 changed files with 858 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ All notable changes to MiniJinja are documented here.
config file and environment variable support. #602
- `minijinja-cli` now supports template source passed by parameter for
simple cases. #606
- `minijinja-cli` now has a `--syntax-help` argument that prints out the
primer on the syntax. #607
- Made the c-bindings compatible with wasm compilation. #603
- `String`/`Cow<str>` argument types will no longer implicitly convert
keyword arguments to string form. This was an unintended foot gun. #605
Expand Down
2 changes: 2 additions & 0 deletions minijinja-cli/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
update-syntax-docs:
uv run ./generate-syntax-docs.py
130 changes: 130 additions & 0 deletions minijinja-cli/generate-syntax-docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# /// script
# requires-python = ">=3.12"
# dependencies = [
# "markdownify==0.13.1",
# "beautifulsoup4==4.12.3",
# "soupsieve==2.6"
# ]
# ///


import os
from pathlib import Path
from bs4 import BeautifulSoup
from markdownify import markdownify, MarkdownConverter


class IndentedCodeMarkdownConverter(MarkdownConverter):
def convert_pre(self, el, text, convert_as_inline):
if not text:
return ''
code = text.rstrip()
lines = code.splitlines()
indented_lines = [' ' + line for line in lines]
return '\n'.join(indented_lines) + '\n'


def main():
doc_path = (
Path(__file__).parent.parent
/ "target"
/ "doc"
/ "minijinja"
/ "syntax"
/ "index.html"
)

with open(doc_path, "r", encoding="utf-8") as file:
html_content = file.read()

soup = BeautifulSoup(html_content, "html.parser")
main_content = soup.find(id="main-content").find(class_="docblock")

# Remove all "a.doc-anchor" elements from main_content
for anchor in main_content.select("a.doc-anchor"):
anchor.decompose()

# Convert all other links into spans
for link in main_content.find_all("a"):
span = soup.new_tag("span")
span.string = link.text
link.replace_with(span)

# Find the h2 with id "synopsis"
synopsis_h2 = main_content.find("h2", id="synopsis")

# Convert h2 to h1, h3 to h2, h4 to h3, etc.
for tag in main_content.find_all(['h2', 'h3', 'h4', 'h5', 'h6']):
new_level = int(tag.name[1]) - 1
if new_level >= 1:
tag.name = f'h{new_level}'

# Change the first <h1>Synopsis</h1> to <h1>Syntax Reference</h1>
synopsis_h2.string = 'Syntax Reference'

# Remove all elements before the synopsis h2
for element in list(synopsis_h2.previous_siblings):
if isinstance(element, str):
element.extract()
else:
element.decompose()

# Clean up whitespace between paragraphs
for paragraph in main_content.find_all('p'):
# Remove extra newlines and spaces after each paragraph
next_sibling = paragraph.next_sibling
while isinstance(next_sibling, str) and next_sibling.strip() == '':
next_sibling.extract()
next_sibling = paragraph.next_sibling

# Ensure there's exactly one newline after each paragraph
if paragraph.next_sibling:
paragraph.insert_after('\n')

# Remove any remaining standalone newlines or spaces
for element in main_content.contents:
if isinstance(element, str) and element.strip() == '':
element.extract()

# Remove the last newline in all <code> tags
for code_block in main_content.find_all('code'):
if code_block.string and code_block.string.endswith('\n'):
code_block.string = code_block.string.rstrip('\n')

# Replace all div.example-wrap with its children
for example_wrap in main_content.select('div.example-wrap'):
example_wrap.unwrap()

# Convert all <code> tags in headlines to <span>
for headline in main_content.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6']):
for code_tag in headline.find_all('code'):
span_tag = soup.new_tag('span')
span_tag.string = code_tag.string
code_tag.replace_with(span_tag)

# Add <br><br> after each <pre> tag (fixes some rendering on conversion)
for pre_tag in main_content.find_all('pre'):
br_tag = soup.new_tag('br')
pre_tag.insert_after(br_tag)

# Remove paragraphs that start with <strong>Feature:</strong>
for paragraph in main_content.find_all('p'):
if paragraph.strong and paragraph.strong.string and paragraph.strong.string.startswith('Feature:'):
paragraph.decompose()

markdown_content = IndentedCodeMarkdownConverter(
escape_underscores=False,
escape_asterisks=False,
escape_misc=False,
wrap=True,
wrap_width=80,
).convert(str(main_content))

output_path = Path(__file__).parent / "src" / "syntax_help.txt"
with open(output_path, 'w', encoding='utf-8') as file:
file.write(markdown_content.rstrip())
print("Regenerated", output_path)


if __name__ == "__main__":
main()
5 changes: 5 additions & 0 deletions minijinja-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,11 @@ pub fn execute() -> Result<i32, Error> {
let matches = make_command().get_matches();
let config = load_config(&matches)?;

if matches.get_flag("syntax-help") {
println!("{}", include_str!("syntax_help.txt"));
return Ok(0);
}

#[cfg(feature = "completions")]
{
if let Some(shell) = matches.get_one::<String>("generate-completion") {
Expand Down
4 changes: 3 additions & 1 deletion minijinja-cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,8 @@ pub(super) fn make_command() -> Command {
.action(ArgAction::HelpShort),
arg!(--"long-help" "Print long help (extended, long explanation texts)")
.action(ArgAction::HelpLong),
arg!(--"syntax-help" "Print syntax help (primer on Jinja2/MiniJinja syntax)")
.action(ArgAction::SetTrue),
arg!(template_file: [TEMPLATE_FILE] "Path to the input template")
.long_help("\
This is the path to the input template in MiniJinja/Jinja2 syntax. \
Expand Down Expand Up @@ -352,7 +354,7 @@ pub(super) fn make_command() -> Command {
]).help_heading("Shell Support"),
])
.before_help("minijinja-cli is a command line tool to render or evaluate jinja2 templates.")
.after_help("For extended help use --long-help, for short help --help.")
.after_help("For a short help use --help, for extended help --long-help, and for help on syntax --syntax-help.")
.about("Pass a template and optionally a file with template variables to render it to stdout.")
.long_about(include_str!("long_help.txt"))
}
Loading

0 comments on commit 4d12ba5

Please sign in to comment.