fetch_tailwind.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. #!/usr/bin/env python3
  2. import re
  3. from dataclasses import dataclass, field
  4. from pathlib import Path
  5. import requests
  6. from bs4 import BeautifulSoup
  7. @dataclass
  8. class Property:
  9. title: str
  10. description: str
  11. members: list[str]
  12. short_members: list[str] = field(init=False)
  13. common_prefix: str = field(init=False)
  14. def __post_init__(self) -> None:
  15. words = [s.split('-') for s in self.members]
  16. prefix = words[0]
  17. for w in words:
  18. i = 0
  19. while i < len(prefix) and i < len(w) and prefix[i] == w[i]:
  20. i += 1
  21. prefix = prefix[:i]
  22. if not prefix:
  23. break
  24. self.short_members = ['-'.join(word[len(prefix):]) for word in words]
  25. self.common_prefix = '-'.join(prefix) + '-' if prefix else ''
  26. @property
  27. def pascal_title(self) -> str:
  28. return ''.join(word.capitalize() for word in re.sub(r'[-/ &]', ' ', self.title).split())
  29. @property
  30. def snake_title(self) -> str:
  31. return '_'.join(word.lower() for word in re.sub(r'[-/ &]', ' ', self.title).split())
  32. properties: list[Property] = []
  33. def get_soup(url: str) -> BeautifulSoup:
  34. path = Path('/tmp') / url.split('/')[-1]
  35. if path.exists():
  36. html = path.read_text()
  37. else:
  38. req = requests.get(url)
  39. html = req.text
  40. path.write_text(html)
  41. return BeautifulSoup(html, 'html.parser')
  42. soup = get_soup('https://tailwindcss.com/docs')
  43. for li in soup.select('li[class="mt-12 lg:mt-8"]'):
  44. title = li.select_one('h5').text
  45. links = li.select('li a')
  46. if title in {'Getting Started', 'Core Concepts', 'Customization', 'Base Styles', 'Official Plugins'}:
  47. continue
  48. print(f'{title}:')
  49. for a in links:
  50. soup = get_soup(f'https://tailwindcss.com{a["href"]}')
  51. title = soup.select_one('#header h1').text
  52. description = soup.select_one('#header .mt-2').text
  53. members = soup.select('.mt-10 td[class*=text-sky-400]')
  54. properties.append(Property(title, description, [p.text.split(' ')[0] for p in members]))
  55. print(f'\t{title} ({len(members)})')
  56. with open(Path(__file__).parent / 'nicegui' / 'tailwind.py', 'w') as f:
  57. f.write('from __future__ import annotations\n')
  58. f.write('\n')
  59. f.write('from typing import TYPE_CHECKING, List, Optional, overload\n')
  60. f.write('\n')
  61. f.write('from typing_extensions import Literal\n')
  62. f.write('\n')
  63. f.write('if TYPE_CHECKING:\n')
  64. f.write(' from .element import Element\n')
  65. f.write('\n')
  66. for property in properties:
  67. if len(property.members) == 1:
  68. continue
  69. f.write(f'{property.pascal_title} = Literal[\n')
  70. for short_member in property.short_members:
  71. f.write(f" '{short_member}',\n")
  72. f.write(']\n')
  73. f.write('\n')
  74. f.write('TailwindClass = Literal[\n')
  75. for property in properties:
  76. for member in property.members:
  77. f.write(f" '{member}',\n")
  78. f.write(']\n')
  79. f.write('\n')
  80. f.write('\n')
  81. f.write('class PseudoElement:\n')
  82. f.write('\n')
  83. f.write(' def __init__(self) -> None:\n')
  84. f.write(' self._classes: List[str] = []\n')
  85. f.write('\n')
  86. f.write(' def classes(self, add: str) -> None:\n')
  87. f.write(' self._classes.append(add)\n')
  88. f.write('\n')
  89. f.write('\n')
  90. f.write('class Tailwind:\n')
  91. f.write('\n')
  92. f.write(" def __init__(self, _element: Optional['Element'] = None) -> None:\n")
  93. f.write(' self.element = _element or PseudoElement()\n')
  94. f.write('\n')
  95. f.write(' @overload\n')
  96. f.write(' def __call__(self, Tailwind) -> Tailwind:\n')
  97. f.write(' ...\n')
  98. f.write('\n')
  99. f.write(' @overload\n')
  100. f.write(' def __call__(self, *classes: TailwindClass) -> Tailwind:\n')
  101. f.write(' ...\n')
  102. f.write('\n')
  103. f.write(' def __call__(self, *args) -> Tailwind:\n')
  104. f.write(' if isinstance(args[0], Tailwind):\n')
  105. f.write(' args[0].apply(self.element)\n')
  106. f.write(' else:\n')
  107. f.write(" self.element.classes(' '.join(args))\n")
  108. f.write(' return self\n')
  109. f.write('\n')
  110. f.write(" def apply(self, element: 'Element') -> None:\n")
  111. f.write(' element._classes.extend(self.element._classes)\n')
  112. f.write(' element.update()\n')
  113. for property in properties:
  114. f.write('\n')
  115. if len(property.members) == 1:
  116. f.write(f" def {property.snake_title}(self) -> 'Tailwind':\n")
  117. f.write(f' """{property.description}"""\n')
  118. f.write(f" self.element.classes('{property.members[0]}')\n")
  119. f.write(f' return self\n')
  120. else:
  121. f.write(f" def {property.snake_title}(self, value: {property.pascal_title}) -> 'Tailwind':\n")
  122. f.write(f' """{property.description}"""\n')
  123. f.write(f" self.element.classes('{property.common_prefix}' + value)\n")
  124. f.write(f' return self\n')