Skip to content

Commit

Permalink
docs(examples) Add table and improve design (#3688)
Browse files Browse the repository at this point in the history
  • Loading branch information
charlesbvll authored Jul 1, 2024
1 parent 4418171 commit d87b737
Show file tree
Hide file tree
Showing 47 changed files with 547 additions and 97 deletions.
4 changes: 1 addition & 3 deletions dev/build-docs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ cd $ROOT
./dev/build-baseline-docs.sh

cd $ROOT
./dev/update-examples.sh
cd examples/doc
make docs
python dev/build-example-docs.py

cd $ROOT
./datasets/dev/build-flwr-datasets-docs.sh
Expand Down
236 changes: 236 additions & 0 deletions dev/build-example-docs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
# Copyright 2024 Flower Labs GmbH. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
"""Build the Flower Example docs."""

import os
import shutil
import re
import subprocess
from pathlib import Path

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
INDEX = os.path.join(ROOT, "examples", "doc", "source", "index.rst")

initial_text = """
Flower Examples Documentation
-----------------------------
Welcome to Flower Examples' documentation. `Flower <https://flower.ai>`_ is
a friendly federated learning framework.
Join the Flower Community
-------------------------
The Flower Community is growing quickly - we're a friendly group of researchers,
engineers, students, professionals, academics, and other enthusiasts.
.. button-link:: https://flower.ai/join-slack
:color: primary
:shadow:
Join us on Slack
Quickstart Examples
-------------------
Flower Quickstart Examples are a collection of demo projects that show how you
can use Flower in combination with other existing frameworks or technologies.
"""

table_headers = (
"\n.. list-table::\n :widths: 50 15 15 15\n "
":header-rows: 1\n\n * - Title\n - Framework\n - Dataset\n - Tags\n\n"
)

categories = {
"quickstart": {"table": table_headers, "list": ""},
"advanced": {"table": table_headers, "list": ""},
"other": {"table": table_headers, "list": ""},
}


def _convert_to_link(search_result):
if "|" in search_result:
if "," in search_result:
result = ""
for part in search_result.split(","):
result += f"{_convert_to_link(part)}, "
return result[:-2]

name, url = search_result.replace('"', "").split("|")
return f"`{name.strip()} <{url.strip()}>`_"

return search_result


def _read_metadata(example):
with open(os.path.join(example, "README.md")) as f:
content = f.read()

metadata_match = re.search(r"^---(.*?)^---", content, re.DOTALL | re.MULTILINE)
if not metadata_match:
raise ValueError("Metadata block not found")
metadata = metadata_match.group(1)

title_match = re.search(r"^title:\s*(.+)$", metadata, re.MULTILINE)
if not title_match:
raise ValueError("Title not found in metadata")
title = title_match.group(1).strip()

labels_match = re.search(r"^labels:\s*\[(.+?)\]$", metadata, re.MULTILINE)
if not labels_match:
raise ValueError("Labels not found in metadata")
labels = labels_match.group(1).strip()

dataset_match = re.search(
r"^dataset:\s*\[(.*?)\]$", metadata, re.DOTALL | re.MULTILINE
)
if not dataset_match:
raise ValueError("Dataset not found in metadata")
dataset = dataset_match.group(1).strip()

framework_match = re.search(
r"^framework:\s*\[(.*?|)\]$", metadata, re.DOTALL | re.MULTILINE
)
if not framework_match:
raise ValueError("Framework not found in metadata")
framework = framework_match.group(1).strip()

dataset = _convert_to_link(re.sub(r"\s+", " ", dataset).strip())
framework = _convert_to_link(re.sub(r"\s+", " ", framework).strip())
return title, labels, dataset, framework


def _add_table_entry(example, label, table_var):
title, labels, dataset, framework = _read_metadata(example)
example_name = Path(example).stem
table_entry = (
f" * - `{title} <{example_name}.html>`_ \n "
f"- {framework} \n - {dataset} \n - {labels}\n\n"
)
if label in labels:
categories[table_var]["table"] += table_entry
categories[table_var]["list"] += f" {example_name}\n"
return True
return False


def _copy_markdown_files(example):
for file in os.listdir(example):
if file.endswith(".md"):
src = os.path.join(example, file)
dest = os.path.join(
ROOT, "examples", "doc", "source", os.path.basename(example) + ".md"
)
shutil.copyfile(src, dest)


def _add_gh_button(example):
gh_text = f'[<img src="_static/view-gh.png" alt="View on GitHub" width="200"/>](https://github.com/adap/flower/blob/main/examples/{example})'
readme_file = os.path.join(ROOT, "examples", "doc", "source", example + ".md")
with open(readme_file, "r+") as f:
content = f.read()
if gh_text not in content:
content = re.sub(
r"(^# .+$)", rf"\1\n\n{gh_text}", content, count=1, flags=re.MULTILINE
)
f.seek(0)
f.write(content)
f.truncate()


def _copy_images(example):
static_dir = os.path.join(example, "_static")
dest_dir = os.path.join(ROOT, "examples", "doc", "source", "_static")
if os.path.isdir(static_dir):
for file in os.listdir(static_dir):
if file.endswith((".jpg", ".png", ".jpeg")):
shutil.copyfile(
os.path.join(static_dir, file), os.path.join(dest_dir, file)
)


def _add_all_entries(index_file):
examples_dir = os.path.join(ROOT, "examples")
for example in sorted(os.listdir(examples_dir)):
example_path = os.path.join(examples_dir, example)
if os.path.isdir(example_path) and example != "doc":
_copy_markdown_files(example_path)
_add_gh_button(example)
_copy_images(example)


def _main():
if os.path.exists(INDEX):
os.remove(INDEX)

with open(INDEX, "w") as index_file:
index_file.write(initial_text)

examples_dir = os.path.join(ROOT, "examples")
for example in sorted(os.listdir(examples_dir)):
example_path = os.path.join(examples_dir, example)
if os.path.isdir(example_path) and example != "doc":
_copy_markdown_files(example_path)
_add_gh_button(example)
_copy_images(example_path)
if not _add_table_entry(example_path, "quickstart", "quickstart"):
if not _add_table_entry(example_path, "comprehensive", "comprehensive"):
if not _add_table_entry(example_path, "advanced", "advanced"):
_add_table_entry(example_path, "", "other")

with open(INDEX, "a") as index_file:
index_file.write(categories["quickstart"]["table"])

index_file.write("\nAdvanced Examples\n-----------------\n")
index_file.write(
"Advanced Examples are mostly for users that are both familiar with "
"Federated Learning but also somewhat familiar with Flower's main "
"features.\n"
)
index_file.write(categories["advanced"]["table"])

index_file.write("\nOther Examples\n--------------\n")
index_file.write(
"Flower Examples are a collection of example projects written with "
"Flower that explore different domains and features. You can check "
"which examples already exist and/or contribute your own example.\n"
)
index_file.write(categories["other"]["table"])

_add_all_entries(index_file)

index_file.write(
"\n.. toctree::\n :maxdepth: 1\n :caption: Quickstart\n :hidden:\n\n"
)
index_file.write(categories["quickstart"]["list"])

index_file.write(
"\n.. toctree::\n :maxdepth: 1\n :caption: Advanced\n :hidden:\n\n"
)
index_file.write(categories["advanced"]["list"])

index_file.write(
"\n.. toctree::\n :maxdepth: 1\n :caption: Others\n :hidden:\n\n"
)
index_file.write(categories["other"]["list"])

index_file.write("\n")


if __name__ == "__main__":
_main()
subprocess.call(f"cd {ROOT}/examples/doc && make html", shell=True)
91 changes: 0 additions & 91 deletions dev/update-examples.sh

This file was deleted.

7 changes: 7 additions & 0 deletions examples/advanced-pytorch/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
---
title: Advanced Flower Example using PyTorch
labels: [advanced, vision, fds]
dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10]
framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html]
---

# Advanced Flower Example (PyTorch)

This example demonstrates an advanced federated learning setup using Flower with PyTorch. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) and it differs from the quickstart example in the following ways:
Expand Down
7 changes: 7 additions & 0 deletions examples/advanced-tensorflow/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
---
title: Advanced Flower Example using TensorFlow/Keras
labels: [advanced, vision, fds]
dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10]
framework: [tensorflow | https://www.tensorflow.org/, Keras | https://keras.io/]
---

# Advanced Flower Example (TensorFlow/Keras)

This example demonstrates an advanced federated learning setup using Flower with TensorFlow/Keras. This example uses [Flower Datasets](https://flower.ai/docs/datasets/) and it differs from the quickstart example in the following ways:
Expand Down
8 changes: 8 additions & 0 deletions examples/android-kotlin/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
---
title: Flower Android Example using Kotlin and TF Lite
labels: [basic, vision, fds]
dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10]
framework: [Android | https://www.android.com/, Kotlin | https://kotlinlang.org/,
TensorFlowLite | https://www.tensorflow.org/lite]
---

# Flower Android Client Example with Kotlin and TensorFlow Lite 2022

This example is similar to the Flower Android Example in Java:
Expand Down
8 changes: 8 additions & 0 deletions examples/android/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
---
title: Flower Android Example using Java and TF Lite
labels: [basic, vision, fds]
dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10]
framework: [Android | https://www.android.com/, Java | https://www.java.com/, TensorFlowLite
| https://www.tensorflow.org/lite]
---

# Flower Android Example (TensorFlowLite)

This example demonstrates a federated learning setup with Android clients in a background thread. The training on Android is done on a CIFAR10 dataset using TensorFlow Lite. The setup is as follows:
Expand Down
7 changes: 7 additions & 0 deletions examples/app-pytorch/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
---
title: Example Flower App using PyTorch
labels: [basic, vision, fds]
dataset: [CIFAR-10 | https://huggingface.co/datasets/uoft-cs/cifar10]
framework: [torch | https://pytorch.org/, torchvision | https://pytorch.org/vision/stable/index.html]
---

# Flower App (PyTorch) 🧪

> 🧪 = This example covers experimental features that might change in future versions of Flower
Expand Down
Loading

0 comments on commit d87b737

Please sign in to comment.