code_utils.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990
  1. # Licensed to the Apache Software Foundation (ASF) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The ASF licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. from __future__ import annotations
  18. import functools
  19. import inspect
  20. import os
  21. from pathlib import Path
  22. from typing import Any
  23. def get_python_source(x: Any) -> str | None:
  24. """Get Python source (or not), preventing exceptions."""
  25. if isinstance(x, str):
  26. return x
  27. if x is None:
  28. return None
  29. source_code = None
  30. if isinstance(x, functools.partial):
  31. source_code = inspect.getsource(x.func)
  32. if source_code is None:
  33. try:
  34. source_code = inspect.getsource(x)
  35. except TypeError:
  36. pass
  37. if source_code is None:
  38. try:
  39. source_code = inspect.getsource(x.__call__)
  40. except (TypeError, AttributeError):
  41. pass
  42. if source_code is None:
  43. source_code = f"No source code available for {type(x)}"
  44. return source_code
  45. def prepare_code_snippet(file_path: str, line_no: int, context_lines_count: int = 5) -> str:
  46. """
  47. Prepare code snippet with line numbers and a specific line marked.
  48. :param file_path: File name
  49. :param line_no: Line number
  50. :param context_lines_count: The number of lines that will be cut before and after.
  51. :return: str
  52. """
  53. code_lines = Path(file_path).read_text().splitlines()
  54. # Prepend line number
  55. code_lines = [
  56. f">{lno:3} | {line}" if line_no == lno else f"{lno:4} | {line}"
  57. for lno, line in enumerate(code_lines, 1)
  58. ]
  59. # # Cut out the snippet
  60. start_line_no = max(0, line_no - context_lines_count - 1)
  61. end_line_no = line_no + context_lines_count
  62. code_lines = code_lines[start_line_no:end_line_no]
  63. # Join lines
  64. code = "\n".join(code_lines)
  65. return code
  66. def get_terminal_formatter(**opts):
  67. """Return the best formatter available in the current terminal."""
  68. if "256" in os.environ.get("TERM", ""):
  69. from pygments.formatters.terminal256 import Terminal256Formatter
  70. formatter = Terminal256Formatter(**opts)
  71. else:
  72. from pygments.formatters.terminal import TerminalFormatter
  73. formatter = TerminalFormatter(**opts)
  74. return formatter