Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
26b3070
Add Sphinx documentation and Read the Docs config
PalashChitnavis Aug 10, 2025
946ec33
Update user guide with usage and issues sections
PalashChitnavis Aug 10, 2025
cb69673
Automate API docs generation in Sphinx config
PalashChitnavis Aug 11, 2025
b5b517c
Remove custom API doc generation from Sphinx config
PalashChitnavis Aug 11, 2025
d31dcc5
Update documentation and add publish guide
PalashChitnavis Aug 18, 2025
4748913
Add examples to documentation
PalashChitnavis Aug 19, 2025
ee55a54
Add developer docs
PalashChitnavis Aug 20, 2025
8d740d8
Add deployment guide and images for GitHub Pages
PalashChitnavis Aug 23, 2025
4c806a2
Revise and expand user documentation for Brian2WASM
PalashChitnavis Aug 23, 2025
6d4fae4
Add detailed docstrings and improve Sphinx config
PalashChitnavis Aug 24, 2025
0827141
Update documentation and add requirements for RTD
PalashChitnavis Aug 28, 2025
f0156c1
Update CLI docstring and remove unused references
PalashChitnavis Aug 28, 2025
8dbe146
Merge branch 'main' into sphinx
PalashChitnavis Aug 28, 2025
7d7fd60
Improve docstrings and Sphinx config
PalashChitnavis Aug 28, 2025
4c38624
Add --skip-install flag to example test command
PalashChitnavis Aug 28, 2025
784ed9e
Remove emoji from status and error messages
PalashChitnavis Aug 28, 2025
b3d69b2
Run tests on push instead of pull request
PalashChitnavis Aug 31, 2025
9ebd7d6
Install local brian2wasm in PR test workflow
PalashChitnavis Aug 31, 2025
813452c
Enable full history and tags in checkout step
PalashChitnavis Aug 31, 2025
25a43b3
Set fixed version for editable install in CI
PalashChitnavis Aug 31, 2025
4350ef7
Update local brian2wasm install in test workflow
PalashChitnavis Aug 31, 2025
ca3fd6e
Install local package with pixi
mstimberg Sep 3, 2025
bcd4455
Make sure that Windows uses nmake
mstimberg Sep 3, 2025
2a73a91
Run tests on pull request events
PalashChitnavis Sep 3, 2025
ba4e6a9
Update setuptools_scm
mstimberg Sep 3, 2025
e1a9eb3
Remove redundant workflow steps
mstimberg Sep 3, 2025
9d6377b
Do not link "advapi32" library
mstimberg Sep 3, 2025
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
17 changes: 6 additions & 11 deletions .github/workflows/test-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,16 @@ jobs:
# Checkout the repository
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: '0'
fetch-tags: 'true'

# Install Pixi
- name: Install pixi
# Install Pixi and workspace
- name: Install pixi and workspace
uses: prefix-dev/[email protected]
with:
cache: false

# Verify Pixi installation
- name: Verify Pixi version
run: pixi --version

# Run pixi install
- name: Install dependencies
run: pixi install --locked

# Run setup
- name: Run setup
run: pixi run setup
Expand All @@ -44,6 +39,6 @@ jobs:
- name: Run example and verify output files
run: |
cd examples
python -m brian2wasm brunel_hakim1999.py --no-server
python -m brian2wasm brunel_hakim1999.py --no-server --skip-install
ls brunel_hakim1999/{brian.js,index.html,wasm_module.{js,wasm},worker.js}
shell: pixi run bash -e {0}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ examples/output/
*.egg-info
pixi.lock
brian2wasm/_version.py
/dist
/dist

/docs/_build
20 changes: 20 additions & 0 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details

# Required
version: 2

# Set the OS, Python version and other tools you might need
build:
os: 'ubuntu-22.04'
tools:
python: "3.12"

# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py

python:
install:
- requirements: rtd-requirements.txt
142 changes: 83 additions & 59 deletions brian2wasm/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,50 +7,45 @@

def main():
"""
Command-line interface for **Brian2Wasm**.

Usage
-----
``python -m brian2wasm <script.py> [--no-server] [--skip-install]``

Parameters
----------
script : str
Path to the user’s Python model. The file **must** end with
``.py`` and must not call ``set_device`` itself – the CLI inserts
the appropriate ``set_device('wasm_standalone', …)`` line
automatically.
--no-server : flag, optional
Generate the WASM/HTML output without starting the local preview
web-server (sets the ``BRIAN2WASM_NO_SERVER`` environment
variable for the subprocess).
--skip-install : flag, optional
Run Brian2WASM without checking or installing EMSDK. Use this if
you are sure EMSDK is already installed and configured in your
environment.

Behaviour
---------
1. Validates that *script* exists and is a ``.py`` file.
2. Looks for an ``<scriptname>.html`` file in the same directory.
* If found, passes the HTML file to ``set_device`` so the custom
template is used.
* Otherwise falls back to the default template.
3. Unless *--skip-install* is given, verifies EMSDK installation
(Pixi/Conda/CONDA_EMSDK_DIR) and attempts to activate it.
4. Prepends the required ``set_device('wasm_standalone', …)`` call to
the script source in-memory.
5. Executes the modified script with its own directory as working
directory, so any relative paths inside the model behave as
expected.

Exit status
-----------
* ``0`` – build finished successfully (and server started unless
*--no-server* was given).
* ``1`` – any error (missing file, not a ``.py`` file, EMSDK not found
or not activated, exception during model execution, etc.).
"""
Command-line interface entry point for Brian2Wasm.

This function validates the given script, injects the required
``set_device('wasm_standalone', …)`` call, ensures EMSDK is
available (unless skipped), and executes the modified script.

Parameters
----------
script : str
Path to the Python model file. The file must exist, end with
``.py``, and must not call ``set_device`` directly, since this
function injects the correct call automatically.
--no-server : bool, optional
If given, generates the WASM/HTML output without starting the
local preview web server. Internally sets the environment
variable ``BRIAN2WASM_NO_SERVER=1``.
--skip-install : bool, optional
If given, skips EMSDK installation and activation checks.
Use this flag when you are certain EMSDK is already installed
and properly configured in your environment.

Raises
------
FileNotFoundError
If the provided script path does not exist.
ValueError
If the provided file is not a Python ``.py`` script.
RuntimeError
If execution of the modified script fails for any reason.
SystemExit
If errors occur during validation or script execution, the
process exits with status code ``1``.

Returns
-------
None
This function is intended as a CLI entry point and does not
return a value.
"""

parser = argparse.ArgumentParser(
description="Brian2WASM CLI"
Expand All @@ -76,10 +71,10 @@ def main():
# Check if the script exists and is a Python file
if not os.path.isfile(script_path):
full_path = os.path.abspath(script_path)
print(f"Error: File '{full_path}' does not exist.", file=sys.stderr)
print(f"Error: File '{full_path}' does not exist.", file=sys.stderr)
sys.exit(1)
if not script_path.endswith(".py"):
print(f"Error: File '{script_path}' is not a Python script (.py).", file=sys.stderr)
print(f"Error: File '{script_path}' is not a Python script (.py).", file=sys.stderr)
sys.exit(1)

if not args.skip_install:
Expand All @@ -101,14 +96,14 @@ def main():

# Inject required lines at the top
if has_html_file:
print(f"HTML file found: '{html_file_path}'")
print(f"HTML file found: '{html_file_path}'")
injection = (
"from brian2 import set_device\n"
"import brian2wasm\n"
f"set_device('wasm_standalone', directory='{script_name}', html_file='{html_file}')\n"
)
else:
print("ℹ️ HTML file not found: using default HTML template.")
print("HTML file not found: using default HTML template.")
injection = (
"from brian2 import set_device\n"
"import brian2wasm\n"
Expand All @@ -125,26 +120,55 @@ def main():
if args.no_server:
os.environ['BRIAN2WASM_NO_SERVER'] = '1'

print(f"📄 Script path: {os.path.abspath(script_path)}")
print(f"📁 Directory: {script_dir}")
print(f"Script path: {os.path.abspath(script_path)}")
print(f"Directory: {script_dir}")
exec_globals = {'__name__': '__main__', '__file__': os.path.abspath(script_path)}
compiled_script = compile(modified_script, script_path, 'exec')
exec(compiled_script, exec_globals)

except Exception as e:
print(f"Error running script: {e}", file=sys.stderr)
print(f"Error running script: {e}", file=sys.stderr)
sys.exit(1)

finally:
os.chdir(original_cwd)


def check_emsdk():
"""
Verify that the Emscripten SDK (EMSDK) is installed and attempt to activate it.

This function checks for EMSDK in the current environment, using either
the system path (``emsdk`` executable) or the ``CONDA_EMSDK_DIR`` variable.
If EMSDK is missing, it prints installation instructions and exits.
If EMSDK is found but not activated, it attempts to activate the latest
version, optionally prompting the user to install and activate it.

Parameters
----------
None
This function takes no arguments.

Raises
------
SystemExit
If EMSDK is not found, not activated, or installation/activation
fails, the process exits with status code ``1``.
RuntimeError
If subprocess execution encounters an unexpected failure during
EMSDK activation.

Returns
-------
None
This function is intended as a setup check and does not
return a value. Its success or failure is indicated by process exit.
"""
emsdk = shutil.which("emsdk")
conda_emsdk_dir = os.environ.get("CONDA_EMSDK_DIR")

if not emsdk and not conda_emsdk_dir:
print("EMSDK and CONDA_EMSDK_DIR not found. That means EMSDK is not installed.")
print("EMSDK and CONDA_EMSDK_DIR not found. That means EMSDK is not installed.")
print(" ➤ If you are using **Pixi**, run:")
print(" pixi add emsdk && pixi install")
print(" ➤ If you are using **Conda**, run:")
Expand All @@ -153,20 +177,20 @@ def check_emsdk():
print(" https://emscripten.org/index.html#")
sys.exit(1)

print(f"EMSDK is installed and CONDA_EMSDK_DIR is found")
print(f"EMSDK is installed and CONDA_EMSDK_DIR is found")

try:
print("🔧 Attempting to activate EMSDK with: emsdk activate latest")
print("Attempting to activate EMSDK with: emsdk activate latest")
result = subprocess.run(["./emsdk", "activate", "latest"], cwd=conda_emsdk_dir, check=False, capture_output=True, text=True)
if result.returncode != 0:
print("Failed to activate EMSDK:")
print("Failed to activate EMSDK:")
choice = input("Do you want to install and activate EMSDK now? (y/n) ")
if choice == 'y':
try:
subprocess.run(["./emsdk", "install", "latest"], cwd=conda_emsdk_dir, check=True)
print("EMSDK install & activation succeeded. You can run the script now.")
print("EMSDK install & activation succeeded. You can run the script now.")
except subprocess.CalledProcessError as e:
print("Failed to activate EMSDK:")
print("Failed to activate EMSDK:")
print(" ➤ Please run the following manually in your terminal and try again:")
print(" cd $CONDA_EMSDK_DIR && ./emsdk install latest && ./emsdk activate latest")
else:
Expand All @@ -175,9 +199,9 @@ def check_emsdk():

sys.exit(1)
else:
print("EMSDK activation succeeded.")
print("EMSDK activation succeeded.")
except Exception as e:
print(f"Error while running EMSDK activation: {e}")
print(f"Error while running EMSDK activation: {e}")
sys.exit(1)


Expand Down
Loading
Loading