Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Move Blog landing page out of 'posts' directory #544

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,7 @@ resource-gallery-submission-input.json
.github/workflows/analytics-api/
portal/metrics/*.png
cisl-vast-pythia-*.json

# Blog post generation
portal/atom.xml
portal/rss.xml
1 change: 0 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ repos:
- id: check-docstring-first
- id: check-json
- id: check-yaml
- id: double-quote-string-fixer

- repo: https://github.com/psf/black
rev: 25.1.0
Expand Down
1 change: 1 addition & 0 deletions environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
- numpy
- matplotlib
- google-api-python-client
- feedgen
- pip
- pip:
- google-analytics-data
7 changes: 7 additions & 0 deletions portal/blog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Blog

Below is the latest news from Project Pythia.

:::{postlist}
:number: 25
:::
14 changes: 11 additions & 3 deletions portal/myst.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,26 +6,34 @@ project:
id: 770e49e5-344a-4c46-adaa-3afb060b2085
authors: Project Pythia Community
github: https://github.com/projectpythia/projectpythia.github.io
plugins:
- type: executable
path: src/blogpost.py

toc:
- file: index.md
- file: about.md
- title: Blog
- file: blog.md
children:
# - pattern: posts/*.md
# Temporary until we have blog infrastructure: explicit list of posts by date (newest first)
- title: "2025"
children:
- file: posts/2025/mystification.md
- file: posts/2025/cookoff2025-website.md
- file: posts/2025/binderhub_status.md
- file: posts/2025/new-cookbooks.md
- title: "2024"
children:
- file: posts/2024/cookoff2024-website.md
- title: "2023"
children:
- file: posts/2023/cookoff2024-savethedate.md
- file: posts/2023/fundraiser.md
- file: posts/2023/cookoff2023.md
- file: contributing.md
- file: cookbook-guide.md
- file: quick-cookbook-guide.md
- file: metrics.md

site:
domains: []
options:
Expand Down
2 changes: 1 addition & 1 deletion portal/posts/2023/cookoff2023.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ During the hackthon, significant additions were made to our Radar Cookbook and i

From our post-hackathon exit survey, everyone enjoyed the event, felt that they learned new skills and that their contributions were valued. One scientist commented, “The hackathon for me has become a great place to get a sense of a community. Seeing people enthusiastic about coding up notebooks that would benefit the research community is a gateway for someone starting to code in Python.” This comment mirrors the efforts of Project Pythia as a community-owned resource.

<img src="../_static/images/posts/projectpythia-cookbook-cookoff.jpeg" alt="Cookoff Image">
<img src="../../_static/images/posts/projectpythia-cookbook-cookoff.jpeg" alt="Cookoff Image">
4 changes: 2 additions & 2 deletions portal/posts/2025/mystification.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ We began the process of transitioning to MyST in the summer of 2024 at the annua
The new MyST architecture was very appealing for several reasons:
- **Sustainability**: Our current Sphinx-based architecture was becoming clunky and hard to maintain as members joined or left the project, and required too much boilerplate code in individual cookbook repos which presented a barrier to would-be new contributors. MyST offered a much more streamlined alternative to keep our community project growing.
- **Staying on the leading edge of best practices**: We are an open-source community resource that teaches open-source coding practices, so it’s important that our own sites continue to be useful models for the broader community.
- **Making cookbooks better!** A lot of the new functionality in MyST is really well suited to the cookbooks, including things like [cross-referencing](https://mystmd.org/guide/cross-references) and [embedding content](Embed & Include Content - MyST Markdown) and automated [bibliographies](https://mystmd.org/guide/citations).
- **Making cookbooks better!** A lot of the new functionality in MyST is really well suited to the cookbooks, including things like [cross-referencing](https://mystmd.org/guide/cross-references) and [embedding content](https://mystmd.org/guide/embed) and automated [bibliographies](https://mystmd.org/guide/citations).
- **Cross-pollination with the core developers!** Having the MyST developers invested in our use-case as a demo as they learn, understand, and develop functionality that will be particularly useful to us (and users that come after) is a really nice feedback loop from both a community and technological stand point.

## MyST for sustainability
### Our aging infrastructure
One example of our maintainability challenge was keeping our bespoke [Pythia-Sphinx theme](https://github.com/ProjectPythia/sphinx-pythia-theme) up-to-date. Upstream dependency updates and cascading syntax changes will always be a concern for the open source community. Combine that with browser default settingschanging since the birth of this project in 2020 (particularly for dark-mode), and there were many HTML and CSS customizations that were no longer displaying as intended. For this reason, we decided to stick as closely to the default [MyST book-theme](https://mystmd.org/guide/website-templates) as serves our purposes. The fewer moving pieces for a new contributor in our open source community to have to be spun up on the better. And with our current collaborations with the MyST team, it’s better to put effort into helping to improve the core tools rather than create unique new customizations.
One example of our maintainability challenge was keeping our bespoke [Pythia-Sphinx theme](https://github.com/ProjectPythia/sphinx-pythia-theme) up-to-date. Upstream dependency updates and cascading syntax changes will always be a concern for the open source community. Combine that with browser default settings changing since the birth of this project in 2020 (particularly for dark-mode), and there were many HTML and CSS customizations that were no longer displaying as intended. For this reason, we decided to stick as closely to the default [MyST book-theme](https://mystmd.org/guide/website-templates) as serves our purposes. The fewer moving pieces for a new contributor in our open source community to have to be spun up on the better. And with our current collaborations with the MyST team, it’s better to put effort into helping to improve the core tools rather than create unique new customizations.

### Repository sprawl
Another maintainability challenge was propagating changes across many GitHub repositories. Within the [Project Pythia Github organization](https://github.com/ProjectPythia/) we currently have 75 different repositories, the vast majority of which contain some website source under the big trenchcoat masquerading as one single Project Pythia website. Each repository is deployed within the domain, but there are separate repositories for our [home page](https://projectpythia.org/), [Foundations book](https://foundations.projectpythia.org/), [resource](https://projectpythia.org/resource-gallery/) and [Cookbooks galleries](https://cookbooks.projectpythia.org/), and for each individual Cookbook. With the Sphinx infrastructure, while the site theming could be abstracted into its own package, other changes to the site configuration or appearance, specifically of the links included in the top nav-bar or footer, would have to be individually updated in every single repository for consistency. We could update our Cookbook Template repository, but GitHub has no one way of sending those divergent-git-history changes to the various Cookbook repositories that leveraged that template. The MyST [`extends` keyword](https://mystmd.org/guide/frontmatter#composing-myst-yml) in the configuration file allows us to not only abstract theming, but also configuration commands and content. Future changes to the site navbar will only have to be made in one place, and individual Cookbook authors will be able to focus on their own content with much reduced boilerplate!
Expand Down
8 changes: 4 additions & 4 deletions portal/posts/2025/new-cookbooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,11 @@ This Cookbook covers how to work with wavelets in Python. Wavelets are a powerfu

### In pictures

<img src="../_static/images/posts/IMG_5686.jpeg" alt="2024 Cookoff group photo" width=900>
<img src="../../_static/images/posts/IMG_5686.jpeg" alt="2024 Cookoff group photo" width=900>
<br>
<img src="../_static/images/posts/IMG_5854.jpeg" alt="Breakout group" width=250>
<img src="../_static/images/posts/IMG_6546.jpg" alt="Cookoff attendees at Mesa Lab" width=250>
<img src="../_static/images/posts/IMG_6055.jpeg" alt="Virtual presentation" width=250>
<img src="../../_static/images/posts/IMG_5854.jpeg" alt="Breakout group" width=250>
<img src="../../_static/images/posts/IMG_6546.jpg" alt="Cookoff attendees at Mesa Lab" width=250>
<img src="../../_static/images/posts/IMG_6055.jpeg" alt="Virtual presentation" width=250>

### By the numbers

Expand Down
168 changes: 168 additions & 0 deletions portal/src/blogpost.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/usr/bin/env python3
import argparse
import json
import re
import sys
from pathlib import Path

import pandas as pd
import unist as u
from feedgen.feed import FeedGenerator
from yaml import safe_load

DEFAULTS = {"number": 10}

root = Path(__file__).parent.parent

# Aggregate all posts from the markdown and ipynb files
posts = []
for ifile in root.rglob("posts/**/*.md"):
if "drafts" in str(ifile):
continue

text = ifile.read_text()
try:
_, meta, content = text.split("---", 2)
except Exception:
print(f"Skipping file with error: {ifile}", file=sys.stderr)
continue

# Load in YAML metadata
meta = safe_load(meta)
meta["path"] = ifile.relative_to(root).with_suffix("")
if "title" not in meta:
lines = text.splitlines()
for ii in lines:
if ii.strip().startswith("#"):
meta["title"] = ii.replace("#", "").strip()
break

# Summarize content
skip_lines = ["#", "--", "%", "++"]
content = "\n".join(
ii
for ii in content.splitlines()
if not any(ii.startswith(char) for char in skip_lines)
)

N_WORDS = 50
content_no_links = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", content)
content_no_bold = re.sub(r"\*\*", "", content_no_links)
words = " ".join(content_no_bold.split(" ")[:N_WORDS])

if "author" not in meta:
meta["author"] = "Project Pythia Team"
meta["content"] = meta.get("description", words)
posts.append(meta)
posts = pd.DataFrame(posts)
posts["date"] = pd.to_datetime(posts["date"]).dt.tz_localize("UTC")
posts = posts.dropna(subset=["date"])
posts = posts.sort_values("date", ascending=False)

# Generate an RSS feed
fg = FeedGenerator()
fg.id("https://projectpythia.org/")
fg.title("Project Pythia blog")
fg.author({"name": "Project Pytia Team", "email": "[email protected]"})
fg.link(href="https://projectpythia.org/", rel="alternate")
fg.logo("https://projectpythia.org/_static/profile.jpg")
fg.subtitle("Project Pythia blog!")
fg.link(href="https://projectpythia.org/", rel="self")
fg.language("en")

# Add all my posts to it
for ix, irow in posts.iterrows():
fe = fg.add_entry()
fe.id(f"https://projectpythia.org/{irow['path']}")
fe.published(irow["date"])
fe.title(irow["title"])
fe.link(href=f"https://projectpythia.org/{irow['path']}")
fe.content(content=irow["content"])

# Write an RSS feed with latest posts
fg.atom_file(root / "atom.xml", pretty=True)
fg.rss_file(root / "rss.xml", pretty=True)

plugin = {
"name": "Blog Post list",
"directives": [
{
"name": "postlist",
"doc": "An example directive for showing a nice random image at a custom size.",
"alias": ["bloglist"],
"arg": {},
"options": {
"number": {
"type": "int",
"doc": "The number of posts to include",
}
},
}
],
}

children = []
for ix, irow in posts.iterrows():
children.append(
{
"type": "card",
"url": f"/{irow['path'].with_suffix('')}",
"children": [
{"type": "cardTitle", "children": [u.text(irow["title"])]},
{"type": "paragraph", "children": [u.text(irow["content"])]},
{
"type": "footer",
"children": [
u.strong([u.text("Date: ")]),
u.text(f"{irow['date']:%B %d, %Y} | "),
u.strong([u.text("Author: ")]),
u.text(f"{irow['author']}"),
],
},
],
}
)


def declare_result(content):
"""Declare result as JSON to stdout

:param content: content to declare as the result
"""

# Format result and write to stdout
json.dump(content, sys.stdout, indent=2)
# Successfully exit
raise SystemExit(0)


def run_directive(name, data):
"""Execute a directive with the given name and data

:param name: name of the directive to run
:param data: data of the directive to run
"""
assert name == "postlist"
opts = data["node"].get("options", {})
number = int(opts.get("number", DEFAULTS["number"]))
output = children[:number]
return output


if __name__ == "__main__":
parser = argparse.ArgumentParser()
group = parser.add_mutually_exclusive_group()
group.add_argument("--role")
group.add_argument("--directive")
group.add_argument("--transform")
args = parser.parse_args()

if args.directive:
data = json.load(sys.stdin)
declare_result(run_directive(args.directive, data))
elif args.transform:
raise NotImplementedError
elif args.role:
raise NotImplementedError
else:
declare_result(plugin)
80 changes: 80 additions & 0 deletions portal/src/unist.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Copied from:
https://github.com/projectpythia-mystmd/cookbook-gallery/blob/main/unist.py
"""


# Node Creation Tools
def text(value, **opts):
return {"type": "text", "value": value, **opts}


def strong(children, **opts):
return {"type": "strong", "children": children, **opts}


def link(children, url, **opts):
return {"type": "link", "url": url, "children": children, **opts}


def table(children, **opts):
return {"type": "table", "children": children, **opts}


def table_cell(children, **opts):
return {"type": "tableCell", "children": children, **opts}


def table_row(cells, **opts):
return {"type": "tableRow", "children": cells, **opts}


def span(children, style, **opts):
return {"type": "span", "children": children, "style": style, **opts}


def definition_list(children, **opts):
return {"type": "definitionList", "children": children, **opts}


def definition_term(children, **opts):
return {"type": "definitionTerm", "children": children, **opts}


def definition_description(children, **opts):
return {"type": "definitionDescription", "children": children, **opts}


def list_(children, ordered=False, spread=False, **opts):
return {
"type": "list",
"ordered": ordered,
"spread": spread,
"children": children,
**opts,
}


def list_item(children, spread=True, **opts):
return {"type": "listItem", "spread": spread, "children": children, **opts}


def image(url, **opts):
return {"type": "image", "url": url, **opts}


def grid(columns, children, **opts):
return {"type": "grid", "columns": columns, "children": children, **opts}


def div(children, **opts):
return {"type": "div", "children": children, **opts}


def find_all_by_type(parent: dict, type_: str):
for node in parent["children"]:
if node["type"] == type_:
yield node
if "children" not in node:
continue
yield from find_all_by_type(node, type_)
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ select = B,C,E,F,W,T4,B9

[isort]
known_first_party=
known_third_party=
known_third_party=feedgen,pandas,unist,yaml
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
Expand Down