Skip to content

Commit cdd143f

Browse files
committed
Fixing merge conflicts.
2 parents 243e455 + 70f1730 commit cdd143f

68 files changed

Lines changed: 1961 additions & 788 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

autodocs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
_build/
22
generated/
33
apidocs/
4+
module_readmes/

autodocs/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ API
22
===
33

44
.. toctree::
5+
:maxdepth: 4
56

67
apidocs/index

autodocs/concepts.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
Concepts
2+
=============
3+
4+
Explanations of key ideas, principles, and background knowledge.
5+
Follow this recommended sequence to build context before diving into
6+
implementation details:
7+
8+
- :doc:`History <docs/concepts/history>` - establishes the background and
9+
problem space the project is addressing.
10+
- :doc:`System Design <docs/concepts/system_design>` - explains how the product
11+
strategy and user needs translate into an overall system approach.
12+
- :doc:`Architecture <docs/concepts/architecture>` - outlines the concrete
13+
architecture that implements the system design.
14+
- :doc:`Technologies <docs/concepts/technologies>` - surveys the primary tools
15+
and platforms we rely on to realize the architecture.
16+
- :doc:`System Settings <docs/concepts/system_settings>` - describes how the
17+
system loads global and cascading settings.
18+
- :doc:`Events <docs/concepts/events>` - introduces the event model that drives
19+
data flowing through the system.
20+
- :doc:`Reducers <docs/concepts/reducers>` - details how incoming events are
21+
aggregated into the state our experiences depend on.
22+
- :doc:`Communication Protocol <docs/concepts/communication_protocol>` - discusses how
23+
the system queries data from reducers for dashboards.
24+
- :doc:`Student Identity Mapping <docs/concepts/student_identity_mapping>` - explain
25+
how learners information is mapped across integrations.
26+
- :doc:`Scaling <docs/concepts/scaling>` - covers strategies for growing the
27+
system once the fundamentals are in place.
28+
- :doc:`Auth <docs/concepts/auth>` - describes authentication considerations
29+
that secure access to the system.
30+
- :doc:`Privacy <docs/concepts/privacy>` - documents how we protect learner data
31+
and comply with privacy expectations.
32+
33+
.. toctree::
34+
:hidden:
35+
:maxdepth: 1
36+
:titlesonly:
37+
38+
docs/concepts/history
39+
docs/concepts/system_design
40+
docs/concepts/architecture
41+
docs/concepts/technologies
42+
docs/concepts/system_settings
43+
docs/concepts/events
44+
docs/concepts/reducers
45+
docs/concepts/communication_protocol
46+
docs/concepts/student_identity_mapping
47+
docs/concepts/scaling
48+
docs/concepts/auth
49+
docs/concepts/privacy

autodocs/conf.py

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
import sys
21
import os
2+
import pathlib
3+
import re
4+
import shutil
5+
import sphinx.util
6+
import sys
7+
import unicodedata
38
# Configuration file for the Sphinx documentation builder.
49
#
510
# For the full list of built-in configuration values, see the documentation:
@@ -9,7 +14,7 @@
914
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
1015

1116
project = 'Learning Observer'
12-
copyright = '2023, Bradley Erickson'
17+
copyright = '2020-2025, Bradley Erickson'
1318
author = 'Bradley Erickson'
1419

1520
# -- General configuration ---------------------------------------------------
@@ -26,6 +31,9 @@
2631
'../modules/writing_observer/writing_observer'
2732
]
2833

34+
autodoc2_output_dir = 'apidocs'
35+
autodoc2_member_order = 'bysource'
36+
2937
source_suffix = {
3038
'.rst': 'restructuredtext',
3139
'.md': 'markdown',
@@ -40,3 +48,143 @@
4048

4149
html_theme = 'alabaster'
4250
html_static_path = ['_static']
51+
52+
LOGGER = sphinx.util.logging.getLogger(__name__)
53+
54+
55+
_MARKDOWN_IMAGE_PATTERN = re.compile(r'!\[[^\]]*\]\(([^)]+)\)')
56+
_RST_IMAGE_PATTERNS = [
57+
re.compile(r'\.\.\s+image::\s+([^\s]+)'),
58+
re.compile(r'\.\.\s+figure::\s+([^\s]+)'),
59+
]
60+
61+
62+
def _extract_local_assets(text):
63+
"""Return relative asset paths referenced in the provided README text."""
64+
65+
asset_paths = set()
66+
for match in _MARKDOWN_IMAGE_PATTERN.findall(text):
67+
asset_paths.add(match)
68+
for pattern in _RST_IMAGE_PATTERNS:
69+
asset_paths.update(pattern.findall(text))
70+
71+
filtered_assets = set()
72+
for raw_path in asset_paths:
73+
candidate = raw_path.strip()
74+
if not candidate:
75+
continue
76+
# Remove optional titles ("path "optional title"") and URL fragments
77+
candidate = candidate.split()[0]
78+
candidate = candidate.split('#', maxsplit=1)[0]
79+
candidate = candidate.split('?', maxsplit=1)[0]
80+
81+
if candidate.startswith(('http://', 'https://', 'data:')):
82+
continue
83+
if candidate.startswith('#'):
84+
continue
85+
86+
filtered_assets.add(candidate)
87+
88+
return sorted(filtered_assets)
89+
90+
91+
def _copy_module_assets(readme_path, destination_dir):
92+
"""Copy image assets referenced by ``readme_path`` into ``destination_dir``."""
93+
94+
module_dir = readme_path.parent.resolve()
95+
readme_text = readme_path.read_text(encoding='utf-8')
96+
asset_paths = _extract_local_assets(readme_text)
97+
for asset in asset_paths:
98+
relative_posix_path = pathlib.PurePosixPath(asset)
99+
if relative_posix_path.is_absolute():
100+
LOGGER.warning(
101+
"Skipping absolute image path %s referenced in %s", asset, readme_path
102+
)
103+
continue
104+
105+
normalized_relative_path = pathlib.Path(*relative_posix_path.parts)
106+
source_path = (module_dir / normalized_relative_path).resolve(strict=False)
107+
108+
try:
109+
source_path.relative_to(module_dir)
110+
except ValueError:
111+
LOGGER.warning(
112+
"Skipping image outside module directory: %s referenced in %s",
113+
asset,
114+
readme_path,
115+
)
116+
continue
117+
118+
if not source_path.exists():
119+
LOGGER.warning(
120+
"Referenced image %s in %s was not found", asset, readme_path
121+
)
122+
continue
123+
124+
destination_path = destination_dir / normalized_relative_path
125+
destination_path.parent.mkdir(parents=True, exist_ok=True)
126+
shutil.copy2(source_path, destination_path)
127+
128+
129+
def _extract_readme_title(readme_path: pathlib.Path) -> str:
130+
"""Return the first Markdown heading in ``readme_path``.
131+
132+
Defaults to the parent directory name if no heading can be found.
133+
"""
134+
135+
try:
136+
for line in readme_path.read_text(encoding='utf-8').splitlines():
137+
stripped = line.strip()
138+
if stripped.startswith('#'):
139+
title = stripped.lstrip('#').strip()
140+
if title:
141+
return title
142+
except OSError as exc: # pragma: no cover - filesystem error propagation
143+
LOGGER.warning("unable to read %s: %s", readme_path, exc)
144+
145+
return readme_path.parent.name
146+
147+
148+
def _slugify(text: str) -> str:
149+
"""Convert ``text`` to a lowercase filename-safe slug."""
150+
151+
normalized = unicodedata.normalize('NFKD', text)
152+
without_diacritics = ''.join(ch for ch in normalized if not unicodedata.combining(ch))
153+
slug = re.sub(r'[^a-z0-9]+', '-', without_diacritics.casefold()).strip('-')
154+
return slug or 'module'
155+
156+
157+
def _copy_module_readmes(app):
158+
"""Populate ``module_readmes`` with module README files and assets."""
159+
160+
docs_root = pathlib.Path(__file__).parent.resolve()
161+
modules_root = docs_root.parent / 'modules'
162+
destination_root = docs_root / 'module_readmes'
163+
164+
if not modules_root.exists():
165+
LOGGER.warning("modules directory %s was not found", modules_root)
166+
return
167+
168+
if destination_root.exists():
169+
shutil.rmtree(destination_root)
170+
destination_root.mkdir(parents=True, exist_ok=True)
171+
172+
readme_info = []
173+
for readme_path in modules_root.glob('*/README.md'):
174+
title = _extract_readme_title(readme_path)
175+
readme_info.append((title, readme_path))
176+
177+
readme_info.sort(key=lambda item: item[0].casefold())
178+
179+
for title, readme_path in readme_info:
180+
module_name = readme_path.parent.name
181+
slug = _slugify(title)
182+
module_destination = destination_root / f'{slug}--{module_name}'
183+
module_destination.mkdir(parents=True, exist_ok=True)
184+
destination_path = module_destination / "README.md"
185+
shutil.copy2(readme_path, destination_path)
186+
_copy_module_assets(readme_path, module_destination)
187+
188+
189+
def setup(app):
190+
app.connect('builder-inited', _copy_module_readmes)

autodocs/development.rst

Lines changed: 0 additions & 21 deletions
This file was deleted.

autodocs/docs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../docs/

autodocs/extension.rst

Lines changed: 0 additions & 6 deletions
This file was deleted.

autodocs/how-to.rst

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
How-to
2+
=============
3+
4+
Practical instructions for achieving specific goals within Learning Observer. Use these guides when you know what outcome you need and want a proven recipe to follow:
5+
6+
- :doc:`Communication Protocol <docs/how-to/communication_protocol>` - How to query data from reducers or system endpoints for dashboards.
7+
- :doc:`Configure Learning Observer <docs/how-to/config>` - Set up credentials, environment variables, and other configuration details required for a smooth deployment.
8+
- :doc:`Build Dashboards <docs/how-to/dashboards>` - Walk through creating dashboards from reducer outputs, including layout choices and data wiring.
9+
- :doc:`LTI <docs/how-to/lti>` - Cover how to install Learning Observer as an LTI application.
10+
- :doc:`Run with Docker <docs/how-to/docker>` - Learn how to containerize the stack, manage images, and operate the project using Docker Compose.
11+
- :doc:`Writing Observer Extension <docs/how-to/extension>` - Install, configure, and validate the Writing Observer browser extension for capturing events.
12+
- :doc:`Interactive Environments <docs/how-to/interactive_environments>` - Connect Learning Observer to Jupyter and other live coding setups for iterative development.
13+
14+
.. toctree::
15+
:hidden:
16+
:maxdepth: 1
17+
:titlesonly:
18+
19+
docs/how-to/communication_protocol.md
20+
docs/how-to/config.md
21+
docs/how-to/dashboards.md
22+
docs/how-to/lti.md
23+
docs/how-to/docker.md
24+
docs/how-to/extension.md
25+
docs/how-to/interactive_environments.md

autodocs/images.rst

Lines changed: 0 additions & 10 deletions
This file was deleted.

autodocs/index.rst

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,21 @@ per-student writing data, and aggegators to make dashboards. We've
1212
tested this in math and writing, but our focus is on writing process
1313
data.
1414

15+
Our documentation is organized into four main categories, each serving a different purpose. You can explore them below:
16+
17+
- :doc:`Tutorials <tutorials>` - Step-by-step guides to help you learn by doing.
18+
- :doc:`Concepts <concepts>` - Explanations of key ideas and background knowledge.
19+
- :doc:`How-To <how-to>` - Practical instructions to solve specific goals.
20+
- :doc:`Reference <reference>` - Detailed API/configuration information.
21+
1522
.. toctree::
16-
:maxdepth: 2
23+
:hidden:
24+
:maxdepth: 3
1725

18-
development
19-
system_design
20-
modules
21-
extension
22-
api
26+
tutorials
27+
concepts
28+
how-to
29+
reference
2330

2431
Additional Information
2532
----------------------

0 commit comments

Comments
 (0)