sorting.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. import re
  2. from typing import TYPE_CHECKING, Any, Callable, Iterable, List, Optional
  3. if TYPE_CHECKING:
  4. from .settings import Config
  5. else:
  6. Config = Any
  7. _import_line_intro_re = re.compile("^(?:from|import) ")
  8. _import_line_midline_import_re = re.compile(" import ")
  9. def module_key(
  10. module_name: str,
  11. config: Config,
  12. sub_imports: bool = False,
  13. ignore_case: bool = False,
  14. section_name: Optional[Any] = None,
  15. straight_import: Optional[bool] = False,
  16. ) -> str:
  17. match = re.match(r"^(\.+)\s*(.*)", module_name)
  18. if match:
  19. sep = " " if config.reverse_relative else "_"
  20. module_name = sep.join(match.groups())
  21. prefix = ""
  22. if ignore_case:
  23. module_name = str(module_name).lower()
  24. else:
  25. module_name = str(module_name)
  26. if sub_imports and config.order_by_type:
  27. if module_name in config.constants:
  28. prefix = "A"
  29. elif module_name in config.classes:
  30. prefix = "B"
  31. elif module_name in config.variables:
  32. prefix = "C"
  33. elif module_name.isupper() and len(module_name) > 1: # see issue #376
  34. prefix = "A"
  35. elif module_name in config.classes or module_name[0:1].isupper():
  36. prefix = "B"
  37. else:
  38. prefix = "C"
  39. if not config.case_sensitive:
  40. module_name = module_name.lower()
  41. length_sort = (
  42. config.length_sort
  43. or (config.length_sort_straight and straight_import)
  44. or str(section_name).lower() in config.length_sort_sections
  45. )
  46. _length_sort_maybe = (str(len(module_name)) + ":" + module_name) if length_sort else module_name
  47. return f"{module_name in config.force_to_top and 'A' or 'B'}{prefix}{_length_sort_maybe}"
  48. def section_key(line: str, config: Config) -> str:
  49. section = "B"
  50. if (
  51. not config.sort_relative_in_force_sorted_sections
  52. and config.reverse_relative
  53. and line.startswith("from .")
  54. ):
  55. match = re.match(r"^from (\.+)\s*(.*)", line)
  56. if match: # pragma: no cover - regex always matches if line starts with "from ."
  57. line = f"from {' '.join(match.groups())}"
  58. if config.group_by_package and line.strip().startswith("from"):
  59. line = line.split(" import", 1)[0]
  60. if config.lexicographical:
  61. line = _import_line_intro_re.sub("", _import_line_midline_import_re.sub(".", line))
  62. else:
  63. line = re.sub("^from ", "", line)
  64. line = re.sub("^import ", "", line)
  65. if config.sort_relative_in_force_sorted_sections:
  66. sep = " " if config.reverse_relative else "_"
  67. line = re.sub(r"^(\.+)", fr"\1{sep}", line)
  68. if line.split(" ")[0] in config.force_to_top:
  69. section = "A"
  70. # * If honor_case_in_force_sorted_sections is true, and case_sensitive and
  71. # order_by_type are different, only ignore case in part of the line.
  72. # * Otherwise, let order_by_type decide the sorting of the whole line. This
  73. # is only "correct" if case_sensitive and order_by_type have the same value.
  74. if config.honor_case_in_force_sorted_sections and config.case_sensitive != config.order_by_type:
  75. split_module = line.split(" import ", 1)
  76. if len(split_module) > 1:
  77. module_name, names = split_module
  78. if not config.case_sensitive:
  79. module_name = module_name.lower()
  80. if not config.order_by_type:
  81. names = names.lower()
  82. line = " import ".join([module_name, names])
  83. elif not config.case_sensitive:
  84. line = line.lower()
  85. elif not config.order_by_type:
  86. line = line.lower()
  87. return f"{section}{len(line) if config.length_sort else ''}{line}"
  88. def sort(
  89. config: Config,
  90. to_sort: Iterable[str],
  91. key: Optional[Callable[[str], Any]] = None,
  92. reverse: bool = False,
  93. ) -> List[str]:
  94. return config.sorting_function(to_sort, key=key, reverse=reverse)
  95. def naturally(
  96. to_sort: Iterable[str], key: Optional[Callable[[str], Any]] = None, reverse: bool = False
  97. ) -> List[str]:
  98. """Returns a naturally sorted list"""
  99. if key is None:
  100. key_callback = _natural_keys
  101. else:
  102. def key_callback(text: str) -> List[Any]:
  103. return _natural_keys(key(text)) # type: ignore
  104. return sorted(to_sort, key=key_callback, reverse=reverse)
  105. def _atoi(text: str) -> Any:
  106. return int(text) if text.isdigit() else text
  107. def _natural_keys(text: str) -> List[Any]:
  108. return [_atoi(c) for c in re.split(r"(\d+)", text)]