"""Fetches MUI component data dynamically from official documentation."""
import time
from typing import Dict, Any, Optional
import requests
from bs4 import BeautifulSoup
class MUIFetcher:
"""Fetches MUI component information dynamically from documentation."""
# Base URL for MUI documentation
BASE_URL = "https://mui.com"
# Core components to fetch
COMPONENT_URLS = {
"Button": "/material-ui/react-button/",
"TextField": "/material-ui/react-text-field/",
"Card": "/material-ui/react-card/",
"AppBar": "/material-ui/react-app-bar/",
"Dialog": "/material-ui/react-dialog/",
"Grid": "/material-ui/react-grid/",
"Chip": "/material-ui/react-chip/",
"Select": "/material-ui/react-select/",
"Typography": "/material-ui/react-typography/",
"Alert": "/material-ui/react-alert/",
"Checkbox": "/material-ui/react-checkbox/",
"Radio": "/material-ui/react-radio-button/",
"Switch": "/material-ui/react-switch/",
"Slider": "/material-ui/react-slider/",
"Tabs": "/material-ui/react-tabs/",
"Menu": "/material-ui/react-menu/",
"Drawer": "/material-ui/react-drawer/",
"Snackbar": "/material-ui/react-snackbar/",
"Tooltip": "/material-ui/react-tooltip/",
"Badge": "/material-ui/react-badge/",
"Avatar": "/material-ui/react-avatar/",
"List": "/material-ui/react-list/",
"Table": "/material-ui/react-table/",
"Paper": "/material-ui/react-paper/",
"Accordion": "/material-ui/react-accordion/",
"Autocomplete": "/material-ui/react-autocomplete/",
"Stepper": "/material-ui/react-stepper/",
"Pagination": "/material-ui/react-pagination/",
"Rating": "/material-ui/react-rating/",
"Skeleton": "/material-ui/react-skeleton/",
"SpeedDial": "/material-ui/react-speed-dial/",
"ToggleButton": "/material-ui/react-toggle-button/",
"Stack": "/material-ui/react-stack/",
"Box": "/material-ui/react-box/",
"Container": "/material-ui/react-container/",
"Breadcrumbs": "/material-ui/react-breadcrumbs/",
"Link": "/material-ui/react-link/",
"ImageList": "/material-ui/react-image-list/",
"Popover": "/material-ui/react-popover/",
"Backdrop": "/material-ui/react-backdrop/",
"CircularProgress": "/material-ui/react-progress/",
"LinearProgress": "/material-ui/react-progress/",
}
def __init__(self):
"""Initialize the fetcher."""
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
})
def _fetch_component_page(self, component_name: str, url_path: str) -> Optional[Dict[str, Any]]:
"""Fetch and parse a component's documentation page."""
try:
url = f"{self.BASE_URL}{url_path}"
response = self.session.get(url, timeout=10)
response.raise_for_status()
soup = BeautifulSoup(response.text, 'lxml')
# Extract description (usually in the first paragraph)
description = f"Material-UI {component_name} component"
main_content = soup.find('main')
if main_content:
first_p = main_content.find('p')
if first_p:
description = first_p.get_text().strip()[:200]
# Extract props from API table (if available)
props = self._extract_props(soup)
# Extract example code
example = self._extract_example(soup, component_name)
return {
"description": description,
"props": props,
"example": example,
"import": f"import {component_name} from '@mui/material/{component_name}';",
"url": url
}
except Exception as e:
print(f"Error fetching {component_name}: {e}")
return None
def _extract_props(self, soup: BeautifulSoup) -> Dict[str, Any]:
"""Extract component props from the API documentation section."""
props = {}
# Look for API/Props section
api_section = soup.find(id=lambda x: x and 'props' in x.lower() if x else False)
if not api_section:
# Try to find by heading
for heading in soup.find_all(['h2', 'h3']):
if 'props' in heading.get_text().lower() or 'api' in heading.get_text().lower():
api_section = heading.find_next('table')
break
if api_section and api_section.name == 'table':
rows = api_section.find_all('tr')
for row in rows[1:]: # Skip header
cols = row.find_all(['td', 'th'])
if len(cols) >= 2:
prop_name = cols[0].get_text().strip()
prop_type = cols[1].get_text().strip() if len(cols) > 1 else "any"
props[prop_name] = prop_type
return props if props else {
"variant": "string",
"color": "string",
"size": "string"
}
def _extract_example(self, soup: BeautifulSoup, component_name: str) -> str:
"""Extract a code example from the documentation."""
# Look for code blocks
code_blocks = soup.find_all('code', class_='language-jsx')
if not code_blocks:
code_blocks = soup.find_all('pre')
for code_block in code_blocks:
code_text = code_block.get_text().strip()
# Look for examples that use this component
if f'<{component_name}' in code_text or f'from "@mui/material/{component_name}"' in code_text:
# Limit example length
lines = code_text.split('\n')[:15]
return '\n'.join(lines)
# Default example if none found
return f"""import {component_name} from '@mui/material/{component_name}';
<{component_name}>
Example content
</{component_name}>"""
def fetch_all_components(self) -> Dict[str, Any]:
"""Fetch all component data dynamically from MUI docs."""
components = {}
for component_name, url_path in self.COMPONENT_URLS.items():
component_data = self._fetch_component_page(component_name, url_path)
if component_data:
components[component_name] = component_data
time.sleep(0.3) # Be nice to the server
return components
def get_components(self) -> Dict[str, Any]:
"""Get component data dynamically from MUI docs."""
try:
return self.fetch_all_components()
except Exception as e:
print(f"Error fetching components: {e}")
return self._get_fallback_components()
def _get_fallback_components(self) -> Dict[str, Any]:
"""Return basic component info as fallback."""
fallback = {}
for component_name in self.COMPONENT_URLS.keys():
fallback[component_name] = {
"description": f"Material-UI {component_name} component",
"props": {"variant": "string", "color": "string"},
"example": f"<{component_name}>Example</{component_name}>",
"import": f"import {component_name} from '@mui/material/{component_name}';"
}
return fallback