The Meson Build System
http://mesonbuild.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
6.2 KiB
161 lines
6.2 KiB
#!/usr/bin/env python3 |
|
|
|
|
|
# Copyright 2018 The Meson development team |
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); |
|
# you may not use this file except in compliance with the License. |
|
# You may obtain a copy of the License at |
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0 |
|
|
|
# Unless required by applicable law or agreed to in writing, software |
|
# distributed under the License is distributed on an "AS IS" BASIS, |
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
|
# See the License for the specific language governing permissions and |
|
# limitations under the License. |
|
|
|
''' |
|
Regenerate markdown docs by using `meson.py` from the root dir |
|
''' |
|
|
|
import argparse |
|
import os |
|
import re |
|
import subprocess |
|
import sys |
|
import textwrap |
|
import json |
|
import typing as T |
|
from pathlib import Path |
|
from urllib.request import urlopen |
|
|
|
PathLike = T.Union[Path,str] |
|
|
|
def _get_meson_output(root_dir: Path, args: T.List) -> str: |
|
env = os.environ.copy() |
|
env['COLUMNS'] = '80' |
|
return subprocess.run([str(sys.executable), str(root_dir/'meson.py')] + args, check=True, capture_output=True, text=True, env=env).stdout.strip() |
|
|
|
def get_commands_data(root_dir: Path) -> T.Dict[str, T.Any]: |
|
usage_start_pattern = re.compile(r'^usage: ', re.MULTILINE) |
|
positional_start_pattern = re.compile(r'^positional arguments:[\t ]*[\r\n]+', re.MULTILINE) |
|
options_start_pattern = re.compile(r'^(optional arguments|options):[\t ]*[\r\n]+', re.MULTILINE) |
|
commands_start_pattern = re.compile(r'^[A-Za-z ]*[Cc]ommands:[\t ]*[\r\n]+', re.MULTILINE) |
|
|
|
def get_next_start(iterators: T.Sequence[T.Any], end: T.Optional[int]) -> int: |
|
return next((i.start() for i in iterators if i), end) |
|
|
|
def normalize_text(text: str) -> str: |
|
# clean up formatting |
|
out = text |
|
out = re.sub(r'\r\n', r'\r', out, flags=re.MULTILINE) # replace newlines with a linux EOL |
|
out = re.sub(r'^ +$', '', out, flags=re.MULTILINE) # remove trailing whitespace |
|
out = re.sub(r'(?:^\n+|\n+$)', '', out) # remove trailing empty lines |
|
return out |
|
|
|
def parse_cmd(cmd: str) -> T.Dict[str, str]: |
|
cmd_len = len(cmd) |
|
usage = usage_start_pattern.search(cmd) |
|
positionals = positional_start_pattern.search(cmd) |
|
options = options_start_pattern.search(cmd) |
|
commands = commands_start_pattern.search(cmd) |
|
|
|
arguments_start = get_next_start([positionals, options, commands], None) |
|
assert arguments_start |
|
|
|
# replace `usage:` with `$` and dedent |
|
dedent_size = (usage.end() - usage.start()) - len('$ ') |
|
usage_text = textwrap.dedent(f'{dedent_size * " "}$ {normalize_text(cmd[usage.end():arguments_start])}') |
|
|
|
return { |
|
'usage': usage_text, |
|
'arguments': normalize_text(cmd[arguments_start:cmd_len]), |
|
} |
|
|
|
def clean_dir_arguments(text: str) -> str: |
|
# Remove platform specific defaults |
|
args = [ |
|
'prefix', |
|
'bindir', |
|
'datadir', |
|
'includedir', |
|
'infodir', |
|
'libdir', |
|
'libexecdir', |
|
'localedir', |
|
'localstatedir', |
|
'mandir', |
|
'sbindir', |
|
'sharedstatedir', |
|
'sysconfdir' |
|
] |
|
out = text |
|
for a in args: |
|
out = re.sub(r'(--' + a + r' .+?)\s+\(default:.+?\)(\.)?', r'\1\2', out, flags=re.MULTILINE|re.DOTALL) |
|
return out |
|
|
|
output = _get_meson_output(root_dir, ['--help']) |
|
commands = {c.strip() for c in re.findall(r'usage:(?:.+)?{((?:[a-z]+,*)+?)}', output, re.MULTILINE|re.DOTALL)[0].split(',')} |
|
commands.remove('help') |
|
|
|
cmd_data = dict() |
|
|
|
for cmd in commands: |
|
cmd_output = _get_meson_output(root_dir, [cmd, '--help']) |
|
cmd_data[cmd] = parse_cmd(cmd_output) |
|
if cmd in ['setup', 'configure']: |
|
cmd_data[cmd]['arguments'] = clean_dir_arguments(cmd_data[cmd]['arguments']) |
|
|
|
return cmd_data |
|
|
|
def generate_hotdoc_includes(root_dir: Path, output_dir: Path) -> None: |
|
cmd_data = get_commands_data(root_dir) |
|
|
|
for cmd, parsed in cmd_data.items(): |
|
for typ in parsed.keys(): |
|
with open(output_dir / (cmd+'_'+typ+'.inc'), 'w', encoding='utf-8') as f: |
|
f.write(parsed[typ]) |
|
|
|
def generate_wrapdb_table(output_dir: Path) -> None: |
|
url = urlopen('https://wrapdb.mesonbuild.com/v2/releases.json') |
|
releases = json.loads(url.read().decode()) |
|
with open(output_dir / 'wrapdb-table.md', 'w', encoding='utf-8') as f: |
|
f.write('| Project | Versions | Provided dependencies | Provided programs |\n') |
|
f.write('| ------- | -------- | --------------------- | ----------------- |\n') |
|
for name, info in releases.items(): |
|
versions = [f'[{v}](https://wrapdb.mesonbuild.com/v2/{name}_{v}/{name}.wrap)' for v in info['versions']] |
|
# Highlight latest version. |
|
versions_str = f'<big>**{versions[0]}**</big><br/>' + ', '.join(versions[1:]) |
|
dependency_names = info.get('dependency_names', []) |
|
dependency_names_str = ', '.join(dependency_names) |
|
program_names = info.get('program_names', []) |
|
program_names_str = ', '.join(program_names) |
|
f.write(f'| {name} | {versions_str} | {dependency_names_str} | {program_names_str} |\n') |
|
|
|
def regenerate_docs(output_dir: PathLike, |
|
dummy_output_file: T.Optional[PathLike]) -> None: |
|
if not output_dir: |
|
raise ValueError(f'Output directory value is not set') |
|
|
|
output_dir = Path(output_dir).resolve() |
|
output_dir.mkdir(parents=True, exist_ok=True) |
|
|
|
root_dir = Path(__file__).resolve().parent.parent |
|
|
|
generate_hotdoc_includes(root_dir, output_dir) |
|
generate_wrapdb_table(output_dir) |
|
|
|
if dummy_output_file: |
|
with open(output_dir/dummy_output_file, 'w', encoding='utf-8') as f: |
|
f.write('dummy file for custom_target output') |
|
|
|
if __name__ == '__main__': |
|
parser = argparse.ArgumentParser(description='Generate meson docs') |
|
parser.add_argument('--output-dir', required=True) |
|
parser.add_argument('--dummy-output-file', type=str) |
|
|
|
args = parser.parse_args() |
|
|
|
regenerate_docs(output_dir=args.output_dir, |
|
dummy_output_file=args.dummy_output_file)
|
|
|