Skip to content
Merged
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
39 changes: 39 additions & 0 deletions .github/workflows/docs-cleanup.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Documentation preview cleanup

# Remove a PR's documentation preview (previews/PR<n>/ on the gh-pages branch)
# once the PR is closed or merged.

on:
pull_request:
types: [closed]

permissions:
contents: write

concurrency:
group: docs-cleanup
cancel-in-progress: false

jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Checkout gh-pages
uses: actions/checkout@v5
with:
ref: gh-pages
continue-on-error: true # nothing to do if gh-pages doesn't exist yet

- name: Remove preview directory
run: |
set -e
dir="previews/PR${{ github.event.number }}"
if [ -d "$dir" ]; then
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git rm -rf "$dir"
git commit -m "docs: remove preview for PR #${{ github.event.number }}"
git push
else
echo "No preview directory '$dir' to remove."
fi
74 changes: 74 additions & 0 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Documentation

on:
push:
branches: [main]
tags: ['*']
pull_request:
workflow_dispatch:

concurrency:
# One docs build per branch/PR; cancel superseded runs.
group: docs-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write # push the built site to the gh-pages branch
pull-requests: write # post the preview-link comment on PRs

jobs:
docs:
name: Build & deploy documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5

- uses: actions/setup-python@v6
with:
python-version: '3.12'

# docs/build.py pip-installs the Sphinx toolchain, builds the narrative
# docs and the autodoc API reference into docs/build/html, and copies the
# result into ./gh-pages. autodoc imports palsparserpy but never calls into
# the C library, so no PALSParserCpp build is needed here.
- name: Build documentation
run: python docs/build.py

# ---- Deploy ----
- name: Deploy main site
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./gh-pages
keep_files: true # preserve previews/ on main deploys

- name: Deploy PR preview
if: github.event_name == 'pull_request'
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./gh-pages
destination_dir: previews/PR${{ github.event.number }}
keep_files: true

- name: Comment preview link
if: github.event_name == 'pull_request'
uses: actions/github-script@v7
with:
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const n = context.issue.number;
const url = `https://${owner.toLowerCase()}.github.io/${repo}/previews/PR${n}/`;
const marker = '<!-- docs-preview -->';
const body = `${marker}\n📖 **Documentation preview** for this PR: ${url}\n\n` +
`_Rebuilt on every push; removed automatically when the PR closes._`;
const { data: comments } = await github.rest.issues.listComments(
{ owner, repo, issue_number: n });
const existing = comments.find(c => c.body && c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body });
} else {
await github.rest.issues.createComment({ owner, repo, issue_number: n, body });
}
75 changes: 75 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
name: Python Tests

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
test:
name: Python ${{ matrix.python-version }} - ${{ matrix.os }}
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
python-version: ['3.9', '3.12']
os: [macos-latest, ubuntu-latest]

steps:
- name: Checkout PALSParserPy repository
uses: actions/checkout@v5
with:
path: PALSParserPy

- name: Checkout PALSParserCpp repository
uses: actions/checkout@v5
with:
repository: pals-project/PALSParserCpp
path: PALSParserCpp

- name: Setup Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

# PALSParserCpp fetches rapidyaml (and the rest) itself via CMake FetchContent,
# so a compiler and CMake are all that is needed. Both runners ship CMake
# preinstalled; only Linux needs a compiler pulled in explicitly.
- name: Install system dependencies (Ubuntu)
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y cmake build-essential

- name: Build C++ library
run: |
cd PALSParserCpp
mkdir -p build
cd build
cmake ..
make -j$(nproc 2>/dev/null || echo 2)
ls -lah
shell: bash

# PALSParserPy finds ../PALSParserCpp/build/ by itself, which is the layout
# the two checkouts above make. pytest puts the checkout on sys.path, so the
# package needs no install; pytest itself does.
- name: Run tests
run: |
cd $GITHUB_WORKSPACE/PALSParserPy
python -m pip install --upgrade pip
python -m pip install pytest
python -m pytest -v
shell: bash

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.os }}-python-${{ matrix.python-version }}
path: |
PALSParserPy/*.log
PALSParserPy/lattice_files/*_out.*
if-no-files-found: ignore
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -216,3 +216,10 @@ __marimo__/

# Streamlit
.streamlit/secrets.toml

# Built documentation site
gh-pages/

# Translator output written by the examples
lattice_files/*.pals_out.*
lattice_files/expand.pals.yaml
89 changes: 89 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,91 @@
# PALSParserPy

Python Interface for Particle Accelerator Language Standard (PALS) files.

## Introduction

`PALSParserPy` is a parser for the Particle Accelerator Language Standard
([PALS](https://github.com/campa-consortium/pals)) for the Python language.

In addition, `PALSParserPy` provides translation functions:

- From `PALS` files to [`Bmad`](https://github.com/bmad-sim/bmad-ecosystem) lattice files.
- From `PALS` files to [`SciBmad`](https://github.com/bmad-sim/SciBmad.jl) lattice files.
- From `PALS` files to [`MAD-X`](https://mad.web.cern.ch/mad/) lattice files.

For a translator from `Bmad` to `PALS`, the `Bmad` based `Tao` program can be used.
A translator from `SciBmad` to `PALS` is planned.

## Status

- 2026-08-05: Initial port of
[PALSParserJ](https://github.com/pals-project/PALSParserJ.jl), the Julia
interface to the same C library.

## Installation

PALSParserPy is a thin Python wrapper around the C library built by
[PALSParserCpp](https://github.com/pals-project/PALSParserCpp), so both
repositories must be cloned side by side and the C library must be built first:

```console
git clone https://github.com/pals-project/PALSParserCpp.git
git clone https://github.com/pals-project/PALSParserPy.git

cd PALSParserCpp && cmake -S . -B build && cmake --build build && cd ..

cd PALSParserPy && pip install -e .
```

`pip install -e .` installs nothing but the package itself — PALSParserPy has no
Python dependencies. If PALSParserCpp lives somewhere other than beside this
checkout, point at it with `PALS_PARSER_CPP_DIR` or `PALS_PARSER_CPP_LIB`.

**See the [Installation guide](https://pals-project.github.io/PALSParserPy/guide/installation.html)
for full step-by-step instructions.**

## Quick start

```python
import palsparserpy as pp

lat = pp.parse_and_expand_pals("lattice_files/ex.pals.yaml")
print(lat.full_expanded) # the expanded lattice, as YAML

pp.parameter_value(lat, "Q1a>length") # one parameter's value
pp.match_names(lat.full_expanded, "B1.*>BendP.e1") # the nodes a name selects

pp.write_bmad_file(pp.pals_to_bmad(pp.parse_file("lattice_files/bta.pals.yaml")),
"bta.bmad")
```

## Examples

For usage examples, see the runnable scripts in the `examples` directory, e.g.

```console
python examples/read_pals.py
```

They insert the repository root on `sys.path`, so they run from a checkout
whether or not the package has been installed.

### Jupyter notebooks

Some examples are also provided as Jupyter notebooks (e.g.
`examples/manipulate_tree.ipynb`). To run them you need Jupyter:

```console
pip install jupyter
jupyter notebook examples/manipulate_tree.ipynb
```

## Tests

```console
pip install -e ".[test]"
pytest
```

The tests import the package from the checkout, so `pip install -e .` is
optional; the C library, however, must be built.
62 changes: 62 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Documentation

The documentation site is one **Sphinx + MyST + Furo** build:

- the narrative docs live in `docs/src/` (MyST Markdown, `conf.py`);
- the API reference is generated by `sphinx.ext.autodoc` from the package
docstrings (`docs/src/api.md`).

(The Julia interface to the same library, PALSParserJ, needs two engines for this
— Documenter for the Julia docstrings and Sphinx for the prose — and stitches
them into one site. Python's autodoc reads the docstrings from inside Sphinx, so
there is nothing to combine.)

`docs/build.py` builds the site into `gh-pages/`.

> **Note:** autodoc imports the `palsparserpy` package to read its docstrings,
> but importing it does not call into the C library, so a compiled
> `libPALSParserCpp` is **not** required to build the docs.

`.github/workflows/docs.yml` runs `docs/build.py` and publishes `gh-pages/` to
the `gh-pages` branch. Pull requests get a full preview at
`previews/PR<number>/` with a link posted as a PR comment; the preview is
deleted on PR close by `.github/workflows/docs-cleanup.yml`.

## One-time repository setup

1. In **Settings → Pages**, set the source to **Deploy from a branch**, branch
**`gh-pages`**, folder **`/ (root)`**.
2. Ensure **Settings → Actions → General → Workflow permissions** is set to
**Read and write permissions** so the workflow can push to `gh-pages` and
comment on PRs.

The published site is at <https://pals-project.github.io/PALSParserPy/>.

> **Note on fork PRs:** previews deploy by pushing to `gh-pages`; PRs opened from
> a *fork* have a read-only token and cannot deploy a preview. PRs from branches
> within this repository work normally.

## Viewing the documentation locally

The easiest way is the helper script [`docs/build_local.sh`](build_local.sh),
which builds the site and serves it:

```sh
docs/build_local.sh
```

Then open <http://localhost:8000/>. Press `Ctrl-C` to stop. Options:
`--port 9000`, `--no-serve`. Requirement: `python3` (the Sphinx toolchain is
pip-installed automatically from `requirements.txt`).

## Building manually

```sh
python docs/build.py # -> gh-pages/
```

Or run Sphinx directly, from `docs/`, after pip-installing `requirements.txt`:

```sh
cd docs && sphinx-build -b html src build/html # -> docs/build/html/
```
Loading
Loading