| #!/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 typing as T |
| from pathlib import Path |
| |
| 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:[\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') as f: |
| f.write(parsed[typ]) |
| |
| 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) |
| |
| if dummy_output_file: |
| with open(output_dir/dummy_output_file, 'w') 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) |