Add CONFIG.md and PCN.md documentation

This commit is contained in:
miruka 2021-01-04 00:31:53 -04:00
parent 8db275ab37
commit 71acca2168
2 changed files with 517 additions and 0 deletions

111
docs/CONFIG.md Normal file
View File

@ -0,0 +1,111 @@
# Configuration
[Folders](#folders) ⬥
[settings.py](#settingspy) ⬥
[accounts.json](#accountsjson)
## Folders
On Linux, the folders are:
- `$XDG_CONFIG_HOME/mirage` or `~/.config/mirage` for config files
- `$XDG_DATA_HOME/mirage` or `~/.local/share/mirage` for user data
- `$XDG_CACHE_HOME/mirage` or `~/.cache/mirage` for cache data
For Flatpak installations, the folders are:
- `~/.var/app/io.github.mirukana.mirage/config/mirage` for config files
- `~/.var/app/io.github.mirukana.mirage/data/mirage` for user data
- `~/.var/app/io.github.mirukana.mirage/cache/mirage` for cache data
The folder locations can also be overriden by these environment variables:
- `$MIRAGE_CONFIG_DIR` for config files
- `$MIRAGE_DATA_DIR` for user data
- `$MIRAGE_CACHE_DIR` for cache data
The user data folder contains saved encryption data, interface states and
[themes](THEMING.md).
The cache data folder contains downloaded files and thumbnails.
## settings.py
A file written in the [PCN format](PCN.md), located in the
[config folder](#folders), which is manually created by the user to configure
the application's behavior.
The default `settings.py`, used when no user-written file exists, documents all
the possible options and can be found at:
- [`src/config/settings.py`][1] in the repository
- `/usr/share/examples/mirage/settings.py` on Linux installations
- `~/.local/share/flatpak/app/io.github.mirukana.mirage/current/active/files/share/examples/mirage/settings.py` for per-user Flatpak installations
- `/var/lib/flatpak/app/io.github.mirukana.mirage/current/active/files/share/examples/mirage/settings.py` for system-wide Flatpak installations
Rather than copying the entire default file, it is recommended to
[`include`](PCN.md#including-built-in-files) it and only add the settings
you want to override.
For example, a user settings file that only changes the theme and some keybinds
could look like this:
```python3
self.include_builtin("config/settings.py")
class General:
theme: str = "Glass.qpl"
class Keys:
reset_zoom = ["Ctrl+Backspace"]
class Messages:
open_links_files = ["Ctrl+Shift+O"]
open_links_files_externally = ["Ctrl+O"]
```
When this file is saved while the application is running, the settings will
automatically be reloaded, except for some options which require a restart.
The default `settings.py` indicates which options require a restart.
[1]: https://github.com/mirukana/mirage/tree/master/src/config/settings.py
## accounts.json
This JSON file, located in the [config folder](#folders), is managed by the
interface and doesn't need to be manually edited, except for changing account
positions via their `order` key.
The `order` key can be any number. If multiple accounts have the same `order`,
they are sorted lexically by user ID.
This file should never be shared, as anyone obtaining your access tokens will
be able to use your accounts.
Within the application, from the Sessions tab of your account's settings,
access tokens can be revoked by signing out sessions,
provided you have the account's password.
Example file:
```json
{
"@user_id:example.org": {
"device_id": "ABCDEFGHIJ",
"enabled": true,
"homeserver": "https://example.org",
"order": 0,
"presence": "online",
"status_msg": "",
"token": "<a long access token>"
},
"@account_2:example.org": {
"device_id": "KLMNOPQRST",
"enabled": true,
"homeserver": "https://example.org",
"order": 1,
"presence": "invisible",
"status_msg": "",
"token": "<a long access token>"
}
}
```

406
docs/PCN.md Normal file
View File

@ -0,0 +1,406 @@
# PCN File Format
Config and theme files are written in the PCN (Python Config Notation) format,
which are organized in a hierarchy of sections and properties.
PCN files can also contain usual Python code, such as imports and
custom functions.
- [Overview](#overview)
- [Sections](#sections)
- [Including Built-in Files](#including-built-in-files)
- [Including User Files](#including-user-files)
- [Inheritance](#inheritance)
- [Properties](#properties)
- [Common Types](#common-types)
- [Expressions](#expressions)
- [Section Access](#section-access)
- [Bracket Access](#bracket-access)
- [GUI files](#gui-files)
## Overview
```python3
# Lines starting with a "#" are considered comments.
# Comments can also be added to the end of normal lines.
# Sections can contain indented properties, other sections or functions.
class Example:
# Properties are written as "name: type = value", examples:
integer_number: int = 5
decimal_number: float = 2.5
character_string: str = "Sample text"
boolean: bool = True # or False
string_list: List[str] = ["foo", "bar", "baz"]
# Property values can be any Python expression, e.g. math operations:
other_number: int = (5 * 4) / 2
# "self" points to the current section, Example, containing other_number.
above_10: bool = self.other_number > 10 # result: False
class Names:
# Property names with characters outside of a-z A-Z 0-9 _ need quoting:
"@alice:example.org": str = "Alice"
"@bob:example.org": str = "Bob"
# Section content can also be accessed with the "self[name]" syntax,
# which works with quoted properties like the ones above:
alice_name: str = self["@alice:example.org"] # result: Alice
# Child sections are also accessible from "self":
child_integer: int = self.Test.integer # result: 5
class Test:
# "parent" refers to the section parent of this one, here "Names".
alice_name: str = parent["@alice:example.org"] # result: "Alice"
integer: int = parent.parent.integer_number # Example.integer_number, which is 5
# Top-level sections can also be accessed directly by names:
alice_name_2: str = Example.Names["@alice:example.org"]
integer_2: int = Example.integer_number
```
## Sections
Sections are defined like Python classes, and can contain properties,
other sections, or Python functions.
A section's name should be written as `CamelCase`, and can only contain
letters, digits and underscores.
The content of a section must be indented by that section's indentation plus
four spaces:
```python3
class FirstSection:
content_spaces: int = 0 + 4
class SectionInsideFirst:
content_spaces: int = 4 + 4
class SecondSection:
content_spaces: int = 0 + 4
```
Empty sections can be created using the `pass` keyword:
```python3
class Empty:
pass
```
### Including Built-in Files
A section, including the file's root (which is treated as a section)
can include files that are supplied by the application using the
`self.include_builtin(path)` function.
`path` is the relative path to a file in the application's source folder,
for example `self.include_builtin("config/settings.py")` refers to
[`src/config/settings.py`][1].
The sections and properties from the included file will be recursively merged,
see [Including User Files](#including-user-files) for an example.
[1]: https://github.com/mirukana/mirage/tree/master/src/config/settings.py
### Including User Files
Similar to [including built-in files](#including-built-in-files), user-written
local files can be included with `self.include_file(path)`, where `path`
is an absolute or relative (from the current file's directory) file path.
Example with two files, `a.py`:
```python3
self.include_file("b.py")
class Shared:
text: str = "Sample"
gets_overriden: str = "A"
class FromA:
number: int = 1
```
and `b.py`:
```python3
class Shared:
gets_overriden: str = "B"
class FromB:
number: int = 2
```
This results in a merged PCN looking like so:
```python3
class Shared:
text: str = "Sample"
gets_overriden: str = "B"
class FromA:
number: int = 1
class FromB:
number: int = 2
```
Include functions can also be used inside a section other than the root.
If `a.py` had the include line inside `Shared`, the result would be:
```python3
class Shared:
text: str = "Sample"
gets_overriden: str = "A"
class FromA:
number: int = 1
class Shared:
gets_overriden: str = "B"
class FromB:
number: int = 2
```
### Inheritance
Like other Python classes, sections can inherit from other sections.
Unlike including files, sections are not merged recursively.
This file:
```python3
class Mixin:
first: bool = True
second: bool = False
class First(Mixin):
pass
class Second(Mixin):
third: int = 100
```
Would be equivalent to:
```python3
class First(Mixin):
first: bool = True
second: bool = False
class Second(Mixin):
first: bool = True
second: bool = False
third: int = 100
```
## Properties
Properties have a name, optional type annotation and value.
Standard property names should be written in `snake_case`.
In most cases, it is recommended to include type annotations, to make clear
what a property's value should be:
```python3
with_type: int = 3
complex_type: Dict[str, int] = {"abc": 1, "def": 2, "ghi": 3}
any_type: Any = None
same_as_above = None
```
If the property's name starts with a digit or contains characters other than
letters, digits or underscores, that name must be quoted:
```python3
"!alice:example.org" = "Alice"
```
Properties with these names can only be accessed by the
[brackets syntax](#bracket-access).
### Common types
- `int`: An integer number, e.g. `4`.
- `float`: Floating point number, e.g. `4.5`. Can also be an integer.
- `str`: String, a piece of text.
If the text contains quotes or backslashes, escape them with a backslash.
Other properties can be included by combining strings or using an f-string:
```python3
escaped: str = "C:\\Users\\Alice \"Foo\" Bar"
number: int = 1
combined: str = "foo " + self.number
fstring: str = f"foo {number}"
```
- `bool`: Boolean, a value that can be either `True` or `False`.
- `None`: A `None` value, represents an absence of choice.
- `Any`: A value that can be of any type.
- `list`: List of values, e.g. `[1, 2, 3]`.
The type can be written as `list` or `List[type]` to specify what type the
list's item should be.
- `tuple`: Similar to lists, but the length cannot be changed once created.
Can be written as `tuple`, `Tuple[type, type]` to specify for example that
the tuple must have two items of certain types, or `Tuple[type, ...]`
for a tuple with any number of items of a certain same type:
```python3
anything: tuple = (1, 2, 3, "foo")
many_ints: Tuple[int, ...] = (1, 2, 3, 4, 5)
three_values: Tuple[int, str, bool] = (1, "example", False)
```
- `dict`: Mapping of keys to values. Can be written as `dict` or
`Dict[key_type, value_type]`:
```python3
anything: dict = {1: 2, "foo": "bar", True: 1.5}
account_order: Dict[str, int] = {"@a:example.com": 1, "@b:example.com": 2}
```
- `Optional[type]`: A value that can be either that type or `None`
- `Union[type1, type2]`: A value that can be one of the type in the `Union`.
The number of types can be more than two.
### Expressions
A property's value can be any Python expression. Properties can also
refer to other properties, no matter what section they belong to or what
order they are defined in.
This PCN code:
```python3
class Section1:
other: int = self.text.lower() * 2 # "exampleexample"
text: str = "Example"
```
Is roughly equivalent to this in standard Python:
```python3
class Section1:
@property
def other(self) -> str:
return self.text.lower() * 2
@property
def text(self) -> str:
return "Example"
```
### Section Access
The current section and its properties are accessed via `self`:
```python3
class Base:
number: int = 10
other: int = self.number * 2 # 20
```
The parent section is accessed via `parent`:
```python3
class Base:
number: int = 10
class Inner:
number: int = parent.number * 2
```
Child sections can be accessed by `self.SectionName`:
```python3
class Base:
number: int = self.Inner.number
class Inner:
number: int = 10
```
Any section (or property, or function) defined at the root/top-level of the
file can be accessed by name:
```python3
class First:
class InsideFirst:
number: int = Second.number * 2 # 20
other: int = Second.InsideSecond.number # 50
class Second:
number: int = 10
class InsideSecond:
number: int = 50
```
The root (which behaves like a section) can also be explicitely accessed
with `self.root`:
```python3
number: int = 10
class First:
root_num: int = self.root.number # Same as just saying "number"
class Second:
first_num: int = self.root.First.root_num # Same as "First.root_num"
```
### Bracket Access
Inner sections and properties can also be accessed by the
`section[name]` syntax. This is the only way to access properties with
non-standard names (as described in [Properties](#properties)):
```python3
class Names:
"!alice:example.org": str = "alice"
class Capitalized:
alice: str = parent["!alice:example.org"].capitalize() # "Alice"
```
The syntax can also be used to access properties dynamically:
```python3
class Names:
alice: str = "Alice"
property_name: str = "alice"
first_person: str = self[property_name]
```
Top-level properties can only be accessed this way using `self.root`:
```python3
"!alice:example.org": str = "Alice"
class Names:
alice: str = self.root["!alice:example.org"] = "Alice"
```
# GUI Files
When properties for PCN files are edited from the user interface
(programmatically or due to user actions), a separate file with a `.gui.json`
extension is created in the same folder.
These files take priority and override properties from the equivalent user
files. They should not be edited by hand.
When a property in the user config file is edited, any equivalent property
in the GUI file is automatically dropped,
to let the user's setting apply again.