|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +import argparse |
| 4 | +import re |
| 5 | +import textwrap |
| 6 | + |
| 7 | +import yaml |
| 8 | + |
| 9 | + |
| 10 | +class LineWrapHelpFormatter(argparse.RawDescriptionHelpFormatter): |
| 11 | + """ |
| 12 | + A helper class for formatting the help messages the CLIs nicely. This implementation |
| 13 | + will preserve indents at the start of a line and interpret newline metacharacters |
| 14 | + accordingly. |
| 15 | +
|
| 16 | + Credits: https://stackoverflow.com/a/35925919 |
| 17 | + """ |
| 18 | + |
| 19 | + def _add_whitespace(self, idx, wspace_idx, text): |
| 20 | + if idx == 0: |
| 21 | + return text |
| 22 | + return (" " * wspace_idx) + text |
| 23 | + |
| 24 | + def _split_lines(self, text, width): |
| 25 | + text_rows = text.splitlines() |
| 26 | + for idx, line in enumerate(text_rows): |
| 27 | + search = re.search(r"\s*[0-9\-]{0,}\.?\s*", line) |
| 28 | + if line.strip() == "": |
| 29 | + text_rows[idx] = " " |
| 30 | + elif search: |
| 31 | + wspace_line = search.end() |
| 32 | + lines = [ |
| 33 | + self._add_whitespace(i, wspace_line, x) |
| 34 | + for i, x in enumerate(textwrap.wrap(line, width)) |
| 35 | + ] |
| 36 | + text_rows[idx] = lines |
| 37 | + return [item for sublist in text_rows for item in sublist] |
| 38 | + |
| 39 | + |
| 40 | +class PrettierDumper(yaml.Dumper): |
| 41 | + """ |
| 42 | + Custom YAML Dumper class that sets `indentless` to False. This generates a YAML |
| 43 | + file that is then compliant with Prettier's formatting style |
| 44 | + """ |
| 45 | + |
| 46 | + def increase_indent(self, flow=False, indentless=False): |
| 47 | + # Force 'indentless=False' so list items align with Prettier |
| 48 | + return super(PrettierDumper, self).increase_indent(flow, indentless=False) |
| 49 | + |
| 50 | + |
| 51 | +def prettier_str_representer(dumper, data): |
| 52 | + """ |
| 53 | + Helper function to format strings according to Prettier's standards: |
| 54 | + - No quoting unless it can be misinterpreted as another data type |
| 55 | + - When quoting, use double quotes unless string already contains double quotes |
| 56 | + """ |
| 57 | + |
| 58 | + def is_implicitly_resolved(value: str) -> bool: |
| 59 | + for ( |
| 60 | + first_char, |
| 61 | + resolvers, |
| 62 | + ) in yaml.resolver.Resolver.yaml_implicit_resolvers.items(): |
| 63 | + if first_char is None or (value and value[0] in first_char): |
| 64 | + for resolver in resolvers: |
| 65 | + if len(resolver) == 3: |
| 66 | + _, regexp, _ = resolver |
| 67 | + else: |
| 68 | + _, regexp = resolver |
| 69 | + if regexp.match(value): |
| 70 | + return True |
| 71 | + return False |
| 72 | + |
| 73 | + # If no quoting is needed, use default plain style |
| 74 | + if not is_implicitly_resolved(data): |
| 75 | + return dumper.represent_scalar("tag:yaml.org,2002:str", data) |
| 76 | + |
| 77 | + # If the string already contains double quotes, fall back to single quotes |
| 78 | + if '"' in data and "'" not in data: |
| 79 | + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="'") |
| 80 | + |
| 81 | + # Otherwise, prefer double quotes |
| 82 | + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"') |
| 83 | + |
| 84 | + |
| 85 | +# Add the custom string representer to PrettierDumper |
| 86 | +PrettierDumper.add_representer(str, prettier_str_representer) |
0 commit comments