diff --git a/docs/CONFIG.md b/docs/CONFIG.md new file mode 100644 index 00000000..31c8c4f9 --- /dev/null +++ b/docs/CONFIG.md @@ -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": "" + }, + "@account_2:example.org": { + "device_id": "KLMNOPQRST", + "enabled": true, + "homeserver": "https://example.org", + "order": 1, + "presence": "invisible", + "status_msg": "", + "token": "" + } +} +``` diff --git a/docs/PCN.md b/docs/PCN.md new file mode 100644 index 00000000..aeb8a839 --- /dev/null +++ b/docs/PCN.md @@ -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.