mirror of
https://github.com/wilsonfreitas/awesome-quant.git
synced 2026-07-27 20:07:50 +00:00
deploy: ca4c8f7691
This commit is contained in:
+132
-35
@@ -26,42 +26,45 @@ def slugify(text: str) -> str:
|
||||
def parse_readme(path: str) -> list[dict]:
|
||||
"""Parse README.md and return a list of project entries (no API data)."""
|
||||
entries = []
|
||||
current_language = ""
|
||||
current_category = ""
|
||||
|
||||
re_h2 = re.compile(r"^## (.+)$")
|
||||
re_h3 = re.compile(r"^### (.+)$")
|
||||
re_entry = re.compile(r"^\s*- \[(.+?)\]\((.+?)\) - (.+)$")
|
||||
re_github = re.compile(r"\[GitHub\]\((https://github\.com/[\w-]+/[-\w\.]+)\)")
|
||||
|
||||
skip_sections = {"Languages"}
|
||||
|
||||
# Strip markdown badge images before parsing
|
||||
re_badge = re.compile(r"\s*!\[[^\]]*\]\([^)]*\)\s*")
|
||||
re_langs = re.compile(r'^((?:`[^`]+`\s*)+)-\s*(.*)$')
|
||||
|
||||
skip_sections = {"Contents"}
|
||||
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
line = re_badge.sub(" ", line).rstrip("\n")
|
||||
|
||||
m = re_h2.match(line)
|
||||
if m:
|
||||
current_language = m.group(1).strip()
|
||||
current_category = ""
|
||||
continue
|
||||
|
||||
m = re_h3.match(line)
|
||||
if m:
|
||||
current_category = m.group(1).strip()
|
||||
continue
|
||||
|
||||
if current_language in skip_sections:
|
||||
if current_category in skip_sections:
|
||||
continue
|
||||
|
||||
m = re_entry.match(line)
|
||||
if m:
|
||||
name = m.group(1).strip()
|
||||
url = m.group(2).strip()
|
||||
desc = m.group(3).strip()
|
||||
raw_desc = m.group(3).strip()
|
||||
|
||||
# Extract inline language tags
|
||||
m_lang = re_langs.match(raw_desc)
|
||||
if m_lang:
|
||||
lang_str = m_lang.group(1)
|
||||
desc = m_lang.group(2)
|
||||
languages = re.findall(r'`([^`]+)`', lang_str)
|
||||
else:
|
||||
desc = raw_desc
|
||||
languages = []
|
||||
|
||||
primary_language = languages[0] if languages else ""
|
||||
|
||||
github_url = ""
|
||||
gh_match = re_github.search(desc)
|
||||
@@ -81,15 +84,15 @@ def parse_readme(path: str) -> list[dict]:
|
||||
|
||||
is_cran = "cran.r-project.org" in url
|
||||
is_pypi = "pypi.org" in url or "pypi.python.org" in url
|
||||
is_commercial = current_language == "Commercial & Proprietary Services"
|
||||
category = current_category or current_language
|
||||
section_slug = slugify(category)
|
||||
is_commercial = current_category == "Commercial & Proprietary Services"
|
||||
section_slug = slugify(current_category)
|
||||
|
||||
entries.append(
|
||||
{
|
||||
"project": name,
|
||||
"language": current_language,
|
||||
"category": category,
|
||||
"language": primary_language,
|
||||
"languages": ",".join(languages),
|
||||
"category": current_category,
|
||||
"section_slug": section_slug,
|
||||
"url": url,
|
||||
"description": desc,
|
||||
@@ -121,6 +124,8 @@ def load_csv(path: str) -> list[dict]:
|
||||
# Extract github_url and repo from CSV data
|
||||
repo = row.get("repo", "")
|
||||
row["github_url"] = f"https://github.com/{repo}" if repo else ""
|
||||
# Ensure languages column exists
|
||||
row["languages"] = row.get("languages", row.get("language", ""))
|
||||
# Clean description: strip [GitHub](url) if present
|
||||
desc = row.get("description", "")
|
||||
desc = re.sub(
|
||||
@@ -148,25 +153,28 @@ def build_tags_html(e: dict) -> str:
|
||||
esc = html.escape
|
||||
tags = []
|
||||
|
||||
# Language tag
|
||||
lang = e.get("language", "")
|
||||
if lang and lang != "Commercial & Proprietary Services" and lang != "Related Lists":
|
||||
lang_slug = slugify(lang)
|
||||
tags.append(
|
||||
f'<button class="tag tag-lang" data-filter-type="language" '
|
||||
f'data-filter-value="{esc(lang)}">{esc(lang_slug)}</button>'
|
||||
)
|
||||
# Language tags (from inline backtick tags)
|
||||
languages_str = e.get("languages", e.get("language", ""))
|
||||
languages = [l.strip() for l in languages_str.split(",") if l.strip()]
|
||||
skip_langs = {"Commercial & Proprietary Services", "Related Lists",
|
||||
"Reproducing Works, Training & Books", "Cross-Language Frameworks"}
|
||||
for lang in languages:
|
||||
if lang and lang not in skip_langs:
|
||||
tags.append(
|
||||
f'<button class="tag tag-lang" data-filter-type="language" '
|
||||
f'data-filter-value="{esc(lang)}">{esc(lang.lower())}</button>'
|
||||
)
|
||||
|
||||
# Section tag
|
||||
section_slug = e.get("section_slug", "")
|
||||
# Category tag
|
||||
category = e.get("category", "")
|
||||
if section_slug and section_slug != slugify(lang):
|
||||
section_slug = e.get("section_slug", "")
|
||||
if section_slug:
|
||||
tags.append(
|
||||
f'<button class="tag tag-section" data-filter-type="category" '
|
||||
f'data-filter-value="{esc(category)}">{esc(section_slug)}</button>'
|
||||
)
|
||||
|
||||
# Source tags
|
||||
# Source tags (unchanged)
|
||||
if e.get("github"):
|
||||
tags.append(
|
||||
'<button class="tag tag-source tag-github" '
|
||||
@@ -191,16 +199,99 @@ def build_tags_html(e: dict) -> str:
|
||||
return "\n ".join(tags)
|
||||
|
||||
|
||||
def build_tag_cloud(entries: list[dict]) -> str:
|
||||
"""Build a tag cloud of popular languages and categories."""
|
||||
from collections import Counter
|
||||
|
||||
# Count language frequencies
|
||||
lang_counts = Counter()
|
||||
for e in entries:
|
||||
langs = [l.strip() for l in e.get("languages", e.get("language", "")).split(",") if l.strip()]
|
||||
lang_counts.update(langs)
|
||||
|
||||
# Remove non-language sections
|
||||
skip = {"Commercial & Proprietary Services", "Related Lists", "Reproducing Works, Training & Books", "Cross-Language Frameworks"}
|
||||
for s in skip:
|
||||
lang_counts.pop(s, None)
|
||||
|
||||
# Count category frequencies
|
||||
cat_counts = Counter(e.get("category", "") for e in entries if e.get("category"))
|
||||
|
||||
# Get top items
|
||||
top_langs = lang_counts.most_common(8) # Top 8 languages
|
||||
top_cats = cat_counts.most_common(6) # Top 6 categories
|
||||
|
||||
if not top_langs and not top_cats:
|
||||
return ""
|
||||
|
||||
# Combine and sort by frequency
|
||||
all_items = []
|
||||
for lang, count in top_langs:
|
||||
all_items.append(("language", lang, count))
|
||||
for cat, count in top_cats:
|
||||
all_items.append(("category", cat, count))
|
||||
|
||||
# Sort by count descending
|
||||
all_items.sort(key=lambda x: x[2], reverse=True)
|
||||
|
||||
# Calculate size scale (1.0 to 1.6x)
|
||||
if all_items:
|
||||
min_count = min(item[2] for item in all_items)
|
||||
max_count = max(item[2] for item in all_items)
|
||||
count_range = max_count - min_count if max_count > min_count else 1
|
||||
else:
|
||||
min_count = max_count = count_range = 1
|
||||
|
||||
tags = []
|
||||
esc = html.escape
|
||||
for tag_type, value, count in all_items:
|
||||
# Calculate font size: 1.0 to 1.6
|
||||
size = 1.0 + ((count - min_count) / count_range * 0.6) if count_range > 0 else 1.0
|
||||
|
||||
if tag_type == "language":
|
||||
tags.append(
|
||||
f'<button class="tag tag-lang tag-cloud-item" '
|
||||
f'data-filter-type="language" data-filter-value="{esc(value)}" '
|
||||
f'style="font-size: {size:.2f}em;">{esc(value.lower())}</button>'
|
||||
)
|
||||
else: # category
|
||||
tags.append(
|
||||
f'<button class="tag tag-section tag-cloud-item" '
|
||||
f'data-filter-type="category" data-filter-value="{esc(value)}" '
|
||||
f'style="font-size: {size:.2f}em;">{esc(value)}</button>'
|
||||
)
|
||||
|
||||
if not tags:
|
||||
return ""
|
||||
|
||||
return f""" <div class="tag-cloud">
|
||||
<div class="tag-cloud-label">Popular filters:</div>
|
||||
<div class="tag-cloud-items">
|
||||
{chr(10).join(" " + tag for tag in tags)}
|
||||
</div>
|
||||
</div>"""
|
||||
|
||||
|
||||
def generate_html(entries: list[dict]) -> str:
|
||||
"""Generate the full HTML page from project entries."""
|
||||
languages = sorted(
|
||||
set(
|
||||
e["language"]
|
||||
lang.strip()
|
||||
for e in entries
|
||||
if e.get("language") and e["language"] != "Languages"
|
||||
for lang in e.get("languages", e.get("language", "")).split(",")
|
||||
if lang.strip()
|
||||
and lang.strip() not in {
|
||||
"Commercial & Proprietary Services",
|
||||
"Related Lists",
|
||||
"Reproducing Works, Training & Books",
|
||||
"Cross-Language Frameworks",
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
# Generate tag cloud
|
||||
tag_cloud_html = build_tag_cloud(entries)
|
||||
|
||||
# Build table rows
|
||||
rows = []
|
||||
for i, e in enumerate(entries, 1):
|
||||
@@ -208,7 +299,6 @@ def generate_html(entries: list[dict]) -> str:
|
||||
name = esc(e["project"])
|
||||
url = esc(e["url"])
|
||||
desc = esc(e["description"])
|
||||
language = esc(e.get("language", ""))
|
||||
category = esc(e.get("category", ""))
|
||||
github_url = esc(e.get("github_url", ""))
|
||||
repo = esc(e.get("repo", ""))
|
||||
@@ -219,6 +309,11 @@ def generate_html(entries: list[dict]) -> str:
|
||||
is_pypi = e.get("pypi", False)
|
||||
is_commercial = e.get("commercial", False)
|
||||
|
||||
# Languages attribute (space-separated)
|
||||
languages_attr = esc(" ".join(
|
||||
l.strip() for l in e.get("languages", e.get("language", "")).split(",") if l.strip()
|
||||
))
|
||||
|
||||
# Stars display
|
||||
stars_html = (
|
||||
f'<span class="stars" title="{stars:,} stars">'
|
||||
@@ -250,7 +345,7 @@ def generate_html(entries: list[dict]) -> str:
|
||||
tags_html = build_tags_html(e)
|
||||
|
||||
rows.append(
|
||||
f""" <tr class="row" data-language="{language}" data-category="{category}" data-sources="{sources_attr}" data-stars="{stars}">
|
||||
f""" <tr class="row" data-languages="{languages_attr}" data-category="{category}" data-sources="{sources_attr}" data-stars="{stars}">
|
||||
<td class="col-num">{i}</td>
|
||||
<td class="col-name">
|
||||
<a href="{url}" target="_blank" rel="noopener">{name}</a>
|
||||
@@ -331,6 +426,8 @@ def generate_html(entries: list[dict]) -> str:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{tag_cloud_html}
|
||||
|
||||
<div class="filter-bar" id="filter-bar" style="display:none">
|
||||
<span class="filter-label">Filtered by:</span>
|
||||
<span class="filter-value" id="filter-value"></span>
|
||||
|
||||
+9494
-9552
File diff suppressed because it is too large
Load Diff
+506
-512
File diff suppressed because it is too large
Load Diff
+14
-5
@@ -73,7 +73,7 @@
|
||||
" " +
|
||||
(expand ? expand.textContent : "")
|
||||
).toLowerCase();
|
||||
const language = row.dataset.language || "";
|
||||
const languages = (row.dataset.languages || "").split(" ");
|
||||
const category = row.dataset.category || "";
|
||||
const sources = row.dataset.sources || "";
|
||||
|
||||
@@ -86,7 +86,7 @@
|
||||
if (show && activeFilter.value) {
|
||||
const ft = activeFilter.type;
|
||||
const fv = activeFilter.value;
|
||||
if (ft === "language" && language !== fv) show = false;
|
||||
if (ft === "language" && !languages.includes(fv)) show = false;
|
||||
if (ft === "category" && category !== fv) show = false;
|
||||
if (ft === "source" && !sources.split(" ").includes(fv)) show = false;
|
||||
}
|
||||
@@ -128,8 +128,8 @@
|
||||
applyFilters();
|
||||
});
|
||||
|
||||
// ===== Tag Click =====
|
||||
tableBody.addEventListener("click", (e) => {
|
||||
// ===== Tag Click Handler =====
|
||||
function handleTagClick(e) {
|
||||
const tag = e.target.closest(".tag");
|
||||
if (tag) {
|
||||
e.stopPropagation();
|
||||
@@ -145,7 +145,16 @@
|
||||
applyFilters();
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Listen for tag clicks on table rows
|
||||
tableBody.addEventListener("click", handleTagClick);
|
||||
|
||||
// Listen for tag clicks on tag cloud (outside table)
|
||||
const tagCloud = $(".tag-cloud");
|
||||
if (tagCloud) {
|
||||
tagCloud.addEventListener("click", handleTagClick);
|
||||
}
|
||||
|
||||
// ===== Row Expand =====
|
||||
tableBody.addEventListener("click", (e) => {
|
||||
|
||||
@@ -363,6 +363,36 @@ button {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* ===== Tag Cloud ===== */
|
||||
.tag-cloud {
|
||||
padding: 1rem 0 0.5rem 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.tag-cloud-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: var(--ink-tertiary);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.tag-cloud-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.tag-cloud-item {
|
||||
transition: transform var(--transition), opacity var(--transition);
|
||||
}
|
||||
|
||||
.tag-cloud-item:hover {
|
||||
transform: scale(1.08);
|
||||
}
|
||||
|
||||
.filter-controls select {
|
||||
padding: 0.625rem 2rem 0.625rem 0.875rem;
|
||||
font-family: var(--font);
|
||||
@@ -703,6 +733,23 @@ button {
|
||||
border-color: rgba(252, 211, 77, 0.25);
|
||||
}
|
||||
|
||||
/* Language-specific tag colors */
|
||||
.tag-lang[data-filter-value="Python"] { background: #306998; color: #FFD43B; border: 1px solid rgba(48, 105, 152, 0.3); }
|
||||
.tag-lang[data-filter-value="R"] { background: #276DC3; color: #fff; border: 1px solid rgba(39, 109, 195, 0.3); }
|
||||
.tag-lang[data-filter-value="Julia"] { background: #9558B2; color: #fff; border: 1px solid rgba(149, 88, 178, 0.3); }
|
||||
.tag-lang[data-filter-value="JavaScript"] { background: #F7DF1E; color: #000; border: 1px solid rgba(247, 223, 30, 0.3); }
|
||||
.tag-lang[data-filter-value="Rust"] { background: #CE412B; color: #fff; border: 1px solid rgba(206, 65, 43, 0.3); }
|
||||
.tag-lang[data-filter-value="C++"] { background: #00599C; color: #fff; border: 1px solid rgba(0, 89, 156, 0.3); }
|
||||
.tag-lang[data-filter-value="C#"] { background: #68217A; color: #fff; border: 1px solid rgba(104, 33, 122, 0.3); }
|
||||
.tag-lang[data-filter-value="Java"] { background: #ED8B00; color: #fff; border: 1px solid rgba(237, 139, 0, 0.3); }
|
||||
.tag-lang[data-filter-value="Golang"] { background: #00ADD8; color: #fff; border: 1px solid rgba(0, 173, 216, 0.3); }
|
||||
.tag-lang[data-filter-value="Haskell"] { background: #5D4F85; color: #fff; border: 1px solid rgba(93, 79, 133, 0.3); }
|
||||
.tag-lang[data-filter-value="Scala"] { background: #DC322F; color: #fff; border: 1px solid rgba(220, 50, 47, 0.3); }
|
||||
.tag-lang[data-filter-value="Ruby"] { background: #CC342D; color: #fff; border: 1px solid rgba(204, 52, 45, 0.3); }
|
||||
.tag-lang[data-filter-value="Matlab"] { background: #E16737; color: #fff; border: 1px solid rgba(225, 103, 55, 0.3); }
|
||||
.tag-lang[data-filter-value="Elixir/Erlang"] { background: #4B275F; color: #fff; border: 1px solid rgba(75, 39, 95, 0.3); }
|
||||
.tag-lang[data-filter-value="PHP"] { background: #777BB4; color: #fff; border: 1px solid rgba(119, 123, 180, 0.3); }
|
||||
|
||||
/* ===== Results ===== */
|
||||
.no-results {
|
||||
text-align: center;
|
||||
|
||||
Reference in New Issue
Block a user