flexmeasures_inflection.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """ FlexMeasures way of handling inflection """
  2. from __future__ import annotations
  3. import re
  4. from typing import Any
  5. import inflect
  6. import inflection
  7. p = inflect.engine()
  8. # Give the inflection module some help for our domain
  9. inflection.UNCOUNTABLES.add("solar")
  10. inflection.UNCOUNTABLES.add("wind")
  11. inflection.UNCOUNTABLES.add("evse")
  12. ACRONYMS = ["EVSE"]
  13. def capitalize(x: str, lower_case_remainder: bool = False) -> str:
  14. """Capitalize string with control over whether to lower case the remainder."""
  15. if lower_case_remainder:
  16. return x.capitalize()
  17. return x[0].upper() + x[1:]
  18. def humanize(word):
  19. return inflection.humanize(word)
  20. def parameterize(word):
  21. """Parameterize the word, so it can be used as a Python or JavaScript variable name.
  22. For example:
  23. >>> parameterize("Acme® EV-Charger™")
  24. 'acme_ev_chargertm'
  25. """
  26. return inflection.parameterize(word).replace("-", "_")
  27. def pluralize(word, count: str | int | None = None):
  28. if word.lower().split()[-1] in inflection.UNCOUNTABLES:
  29. return word
  30. return p.plural(word, count)
  31. def titleize(word):
  32. """Acronym exceptions are not yet supported by the inflection package,
  33. even though Ruby on Rails, of which the package is a port, does.
  34. In most cases it's probably better to use our capitalize function instead of titleize,
  35. because it has less unintended side effects. For example:
  36. >>> word = "two PV panels"
  37. >>> titleize(word)
  38. 'Two Pv Panels'
  39. >>> capitalize(word)
  40. 'Two PV panels'
  41. """
  42. word = inflection.titleize(word)
  43. for ac in ACRONYMS:
  44. word = re.sub(inflection.titleize(ac), ac, word)
  45. return word
  46. def join_words_into_a_list(words: list[str]) -> str:
  47. return p.join(words, final_sep="")
  48. def atoi(text):
  49. """Utility method for the `natural_keys` method."""
  50. return int(text) if text.isdigit() else text
  51. def natural_keys(text: str):
  52. """Support for human sorting.
  53. `alist.sort(key=natural_keys)` sorts in human order.
  54. https://stackoverflow.com/a/5967539/13775459
  55. """
  56. return [atoi(c) for c in re.split(r"(\d+)", text)]
  57. def human_sorted(alist: list, attr: Any | None = None, reverse: bool = False):
  58. """Human sort a list (for example, a list of strings or dictionaries).
  59. :param alist: List to be sorted.
  60. :param attr: Optionally, pass a dictionary key or attribute name to sort by
  61. :param reverse: If True, sorts descending.
  62. Example:
  63. >>> alist = ["PV 10", "CP1", "PV 2", "PV 1", "CP 2"]
  64. >>> sorted(alist)
  65. ['CP 2', 'CP1', 'PV 1', 'PV 10', 'PV 2']
  66. >>> human_sorted(alist)
  67. ['CP1', 'CP 2', 'PV 1', 'PV 2', 'PV 10']
  68. """
  69. if attr is None:
  70. # List of strings, to be sorted
  71. sorted_list = sorted(alist, key=lambda k: natural_keys(str(k)), reverse=reverse)
  72. else:
  73. try:
  74. # List of dictionaries, to be sorted by key
  75. sorted_list = sorted(
  76. alist, key=lambda k: natural_keys(k[attr]), reverse=reverse
  77. )
  78. except TypeError:
  79. # List of objects, to be sorted by attribute
  80. sorted_list = sorted(
  81. alist,
  82. key=lambda k: natural_keys(str(getattr(k, str(attr)))),
  83. reverse=reverse,
  84. )
  85. return sorted_list