From 6476194d72e9424551ee41fd8d980ff3d5cee385 Mon Sep 17 00:00:00 2001 From: dahlo Date: Fri, 25 Oct 2024 09:04:04 +0200 Subject: [PATCH] Added serving and automatic rendering functionallity --- .gitignore | 1 + Dockerfile | 2 ++ README.md | 9 +++++++ serve_quarto.py | 66 +++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+) create mode 100644 serve_quarto.py diff --git a/.gitignore b/.gitignore index 1542b0652..f39a95fd7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ to-do.md /.quarto/ .DS_Store [0-9]*/ +venv/ diff --git a/Dockerfile b/Dockerfile index b958104b8..265b45eb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN apt-get update -y \ libopenblas-base \ libgdal-dev \ curl \ + python3-pip \ && wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb \ && apt-get install -y ./google-chrome-stable_current_amd64.deb \ && rm -rf ./google-chrome-stable_current_amd64.deb \ @@ -23,6 +24,7 @@ RUN apt-get update -y \ && apt-get install -y ./quarto-linux-amd64.deb \ && rm -rf ./quarto-linux-amd64.deb \ && Rscript -e 'install.packages(c("remotes","fontawesome","here","htmlTable","leaflet","readxl","writexl"),repos = "http://cran.us.r-project.org");' \ + && pip3 install --no-cache-dir Flask==2.2.2 Werkzeug==2.2.2 PyYAML==6.0.2 watchdog==5.0.3 \ && rm -rf /var/lib/apt/lists/* \ && mkdir /qmd /.cache \ && chmod 777 /qmd /.cache diff --git a/README.md b/README.md index 83804078e..7f26ae424 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,15 @@ docker run --rm --platform linux/amd64 -u $(id -u):$(id -g) -v ${PWD}:/qmd ghcr. docker run --rm --platform linux/amd64 -u $(id -u):$(id -g) -v ${PWD}:/qmd ghcr.io/nbisweden/workshop-ngsintro:latest quarto render index.qmd ``` +## Serving and automatic rendering + +You can use a Flask server to serve the site, and handle automatic rebuilding of pages when any `.qmd` file is changed. The browser has to be refreshed manually though. + +```bash +# serve the site +docker run --rm --platform linux/amd64 -u $(id -u):$(id -g) -v ${PWD}:/qmd -p 5000:5000 ghcr.io/nbisweden/workshop-ngsintro:latest python3 serve_quarto.py +``` + ## Test scripts This is regarding the directory **scripts**. This directory contains shell scripts for reseq (variant-calling) and rnaseq parts of the workshop. These are intended to be run on UPPMAX. Further instructions on using them are available within the scripts. diff --git a/serve_quarto.py b/serve_quarto.py new file mode 100644 index 000000000..87d77ac73 --- /dev/null +++ b/serve_quarto.py @@ -0,0 +1,66 @@ +from flask import Flask, send_from_directory +import os +import subprocess +import yaml +from watchdog.observers import Observer +from watchdog.events import FileSystemEventHandler +import time + +app = Flask(__name__) +quarto_yml_path = '_quarto.yml' +watched_folder = '.' + +# Function to read the output directory from _quarto.yml +def read_output_dir_from_quarto_yml(quarto_yml_path): + with open(quarto_yml_path, 'r') as file: + quarto_config = yaml.safe_load(file) + return quarto_config.get('project', {}).get('output-dir', 'default-output-folder') + +output_folder = read_output_dir_from_quarto_yml(quarto_yml_path) + +class QuartoHandler(FileSystemEventHandler): + def __init__(self): + self.last_modified_time = {} + + def should_process(self, file_path, cooldown=2): + # Check the time of the last modification for the given file + last_time = self.last_modified_time.get(file_path, 0) + # Get the current time + current_time = time.time() + # If the last modification was more recent than the cooldown, skip processing + if current_time - last_time < cooldown: + return False + # Update the last modification time and proceed + self.last_modified_time[file_path] = current_time + return True + + def on_modified(self, event): + if not event.is_directory and event.src_path.endswith('.qmd'): + if self.should_process(event.src_path): + print(f"Detected changes in {event.src_path}. Rendering...") + # Run the Quarto render command for the specific file + subprocess.run(['quarto', 'render', event.src_path]) + + +# Set up file watcher +event_handler = QuartoHandler() +observer = Observer() +observer.schedule(event_handler, watched_folder, recursive=True) +observer.start() + + +@app.route('/') +def index(): + # Serve the index.html file + return send_from_directory(output_folder, 'index.html') + +@app.route('/') +def serve_file(filename): + # Serve the rendered file + return send_from_directory(output_folder, filename) + + +if __name__ == '__main__': + output_folder = read_output_dir_from_quarto_yml(quarto_yml_path) # Update the output folder on the startup + app.run(host='0.0.0.0', port=5000, debug=True) +