diff --git a/_data/development_nav.yml b/_data/development_nav.yml
index 01ecd80..0e62f39 100644
--- a/_data/development_nav.yml
+++ b/_data/development_nav.yml
@@ -68,6 +68,9 @@
- name: python-moeralib/node
title: moeralib.node
+- name: python-moeralib/universal_location
+ title: moeralib.universal_location
+
- type: end
- name: development-environment
diff --git a/_data/py_universal_location_classes.yml b/_data/py_universal_location_classes.yml
new file mode 100644
index 0000000..c04039c
--- /dev/null
+++ b/_data/py_universal_location_classes.yml
@@ -0,0 +1,111 @@
+classes:
+ - name: UniversalLocation
+ category: class
+ description: Represents location part of a universal Moera URL.
+ fields:
+ - name: node_name
+ type: str | None
+ description: The node name.
+ - name: scheme
+ type: str
+ description: Scheme specifier of the node location.
+ - name: authority
+ type: str | None
+ description: Authority (host name and optional port) of the node location.
+ - name: path
+ type: str | None
+ description: Virtual path at the node.
+ - name: location
+ type: str
+ readonly: true
+ description: Universal Moera location (without query and fragment).
+ - name: query
+ type: str | None
+ description: Query component of the URL.
+ - name: fragment
+ type: str | None
+ description: Fragment identifier of the URL.
+ functions:
+ - name: UniversalLocation(node_name=None, scheme=None, authority=None, path=None, query=None, fragment=None)
+ params:
+ - name: node_name
+ type: str | None
+ optional: true
+ description: the node name
+ - name: scheme
+ type: str | None
+ optional: true
+ description: >
+ scheme specifier of the node location ('https'
, if set to None
or empty)
+ - name: authority
+ type: str | None
+ optional: true
+ description: authority (host name and optional port) of the node location
+ - name: path
+ type: str | None
+ optional: true
+ description: virtual path at the node ('/'
, if set to None
or empty)
+ - name: query
+ type: str | None
+ optional: true
+ description: query component of the URL
+ - name: fragment
+ type: str | None
+ optional: true
+ description: fragment identifier of the URL
+
+constants:
+ - name: REDIRECTOR
+ value: '"moera-page"'
+ description: Host used as redirector in universal URLs.
+
+functions:
+ - name: parse(url)
+ params:
+ - name: url
+ type: str | None
+ description: the URL to be parsed
+ out:
+ type: UniversalLocation
+ description: the parsed location
+ description: Parse the location part (including query and fragment) of a universal URL.
+
+ - name: redirect_to_url(node_name, url=None)
+ params:
+ - name: node_name
+ type: str | None
+ description: the node name
+ - name: url
+ type: str | None
+ optional: true
+ description: the direct URL
+ out:
+ type: str
+ description: the universal URL
+ description: Build a universal Moera URL from the direct URL of a page on a node, adding the node name provided.
+
+ - name: redirect_to(node_name, root_url, path=None, query=None, fragment=None)
+ params:
+ - name: node_name
+ type: str | None
+ description: the node name
+ - name: root_url
+ type: str | None
+ description: the Moera root URL of the node
+ - name: path
+ type: str | None
+ optional: true
+ description: virtual path at the node ('/'
, if set to None
or empty)
+ - name: query
+ type: str | None
+ optional: true
+ description: query component of the URL
+ - name: fragment
+ type: str | None
+ optional: true
+ description: fragment identifier of the URL
+ out:
+ type: str
+ description: the universal URL
+ description: >
+ Build a universal Moera URL from the node name, the Moera root URL of the node, virtual path and other components.
diff --git a/development/python-moeralib/index.md b/development/python-moeralib/index.md
index 2ebb824..56b9910 100644
--- a/development/python-moeralib/index.md
+++ b/development/python-moeralib/index.md
@@ -130,6 +130,46 @@ for story in slice.stories:
print(story.posting.operations.view, story.posting.heading)
```
+## Universal URLs
+
+moeralib.universal_location
+module contains classes and routines for creating and parsing Moera universal
+URLs.
+
+To parse a universal URL, pass it to `parse()` function. It returns a
+`UniversalLocation` instance containing the result of parsing.
+
+```python
+from moeralib.universal_location import parse
+
+uni = parse("https://moera.page/@Alice/alice.moera.blog/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3")
+print(uni.node_name, uni.authority, uni.path)
+```
+
+To build a universal URL from parts, use `redirect_to()` function.
+
+```python
+from moeralib.universal_location import redirect_to
+
+print(redirect_to(
+ node_name="Alice_0",
+ root_url="https://alice.moera.blog/",
+ path="/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3"
+))
+```
+
+`redirect_to_url()` function converts URL of a page on a node to a corresponding
+universal URL.
+
+```python
+from moeralib.universal_location import redirect_to
+
+print(redirect_to_url(
+ "Alice_0",
+ "https://alice.moera.blog/moera/post/69a403ef-b72d-43e0-967e-eab5e8dce9d3"
+))
+```
+
[1]: https://github.com/MoeraOrg/python-moeralib
[2]: https://pypi.org/project/moeralib/
[3]: /development/node-api/authentication.html
diff --git a/development/python-moeralib/universal_location.html b/development/python-moeralib/universal_location.html
new file mode 100644
index 0000000..b433655
--- /dev/null
+++ b/development/python-moeralib/universal_location.html
@@ -0,0 +1,116 @@
+---
+layout: development
+title: Python Library
+up: python-moeralib
+subtitle: moeralib.universal_location
+---
+
+
{{ cn.description }}
+{% endfor %} +{{ fn.description }}
+{{ param.name }}
:
+ {% if param.type %}
+ {{ param.type }}
+ {% endif %}
+ {% if param.class %}
+ {%
+ if param.array == true %}List[{{ param.class }}]{% else %}{{ param.class }}{% endif
+ %}
+ {% endif %}
+ {% if param.description %}
+ – {% if param.optional == true %}(optional){% endif %} {{ param.description }}
+ {% else %}
+ {% if param.optional == true %}– (optional){% endif %}
+ {% endif %}
+ {{ fn.out.type }}
+ {% endif %}
+ {% if fn.out.description %}
+ – {{ fn.out.description }}
+ {% endif %}
+ {{ class.description }}
+ {% for field in class.fields %} +
+ {% if field.type %}
+ {{ field.type }}
–
+ {% endif %}
+ {% if field.readonly == true %}(read only){% endif %} {{ field.description }}
+
{{ fn.description }}
+{{ param.name }}
:
+ {% if param.type %}
+ {{ param.type }}
+ {% endif %}
+ {% if param.class %}
+ {%
+ if param.array == true %}List[{{ param.class }}]{% else %}{{ param.class }}{% endif
+ %}
+ {% endif %}
+ {% if param.struct %}
+ {%
+ if param.array == true %}List[types.{{ param.struct }}]{% else %}types.{{ param.struct }}{% endif
+ %}
+ {% endif %}
+ {% if param.description %}
+ – {% if param.optional == true %}(optional){% endif %} {{ param.description }}
+ {% else %}
+ {% if param.optional == true %}– (optional){% endif %}
+ {% endif %}
+ {{ fn.out.type }}
+ {% endif %}
+ {% if fn.out.struct %}
+ {%
+ if fn.out.array == true %}List[types.{{ fn.out.struct }}]{% else %}types.{{ fn.out.struct }}{% endif
+ %}
+ {% endif %}
+ {% if fn.out.description %}
+ – {{ fn.out.description }}
+ {% endif %}
+