From 0b8b77697b2e461c71774a92095fd07802e842a5 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 11:22:33 +0100 Subject: [PATCH 01/13] Add templating folder and stub files/templates. --- templating/README.md | 21 +++++++++++++++++++++ templating/build.py | 18 ++++++++++++++++++ templating/templates/events.tmpl | 7 +++++++ 3 files changed, 46 insertions(+) create mode 100644 templating/README.md create mode 100644 templating/build.py create mode 100644 templating/templates/events.tmpl diff --git a/templating/README.md b/templating/README.md new file mode 100644 index 00000000..c1992334 --- /dev/null +++ b/templating/README.md @@ -0,0 +1,21 @@ +This folder contains the templates and templating system for creating the spec. +We use the templating system Jinja2 in Python. This was chosen over other +systems such as Handlebars.js and Templetor because we already have a Python +dependency on the spec build system, and Jinja provides a rich set of template +operations beyond basic control flow. + +Installation +------------ +``` + $ pip install Jinja2 +``` + +Running +------- +To build the spec: +``` + $ python build.py +``` + +This will output ``spec.rst`` which can then be fed into the RST->HTML +converter located in ``matrix-doc/scripts``. diff --git a/templating/build.py b/templating/build.py new file mode 100644 index 00000000..b2e4266d --- /dev/null +++ b/templating/build.py @@ -0,0 +1,18 @@ +from jinja2 import Environment, FileSystemLoader +import json + +def jsonify(input): + return json.dumps(input, indent=4) + +env = Environment(loader=FileSystemLoader("templates")) +env.filters["jsonify"] = jsonify + +example = {} +with open("../example.json", "r") as f: + example = json.loads(f.read()) +event = {} +with open("../event_schema.json", "r") as f: + event = json.loads(f.read()) + +template = env.get_template("events.tmpl") +print template.render(example=example, event=event) diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl new file mode 100644 index 00000000..512f7a2e --- /dev/null +++ b/templating/templates/events.tmpl @@ -0,0 +1,7 @@ +{{event.type}} +-------------- +Summary: {{event.summary}} +Type: {{event.parent}} +Description: {{event.desc}} +JSON Format: {{event.content | jsonify}} +Example: {{example.content | jsonify}} From 4e64d9e3405d79393978a07b6a385b525f4b3882 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 13:16:16 +0100 Subject: [PATCH 02/13] Start fleshing out build script --- templating/build.py | 98 +++++++++++++++++++++++++++++---- templating/internal/__init__.py | 21 +++++++ templating/internal/sections.py | 18 ++++++ templating/internal/units.py | 24 ++++++++ 4 files changed, 149 insertions(+), 12 deletions(-) mode change 100644 => 100755 templating/build.py create mode 100644 templating/internal/__init__.py create mode 100644 templating/internal/sections.py create mode 100644 templating/internal/units.py diff --git a/templating/build.py b/templating/build.py old mode 100644 new mode 100755 index b2e4266d..4510fa65 --- a/templating/build.py +++ b/templating/build.py @@ -1,18 +1,92 @@ +#!/usr/bin/env python +""" +Builds the Matrix Specification as restructed text (RST). + +Architecture +============ ++-------+ +----------+ +| units |-+ | sections |-+ ++-------+ |-+ === used to create ==> +----------- | === used to create ==> SPEC + +-------+ | +----------+ + +--------+ +RAW DATA (e.g. json) Blobs of RST + +Units +===== +Units are random bits of unprocessed data, e.g. schema JSON files. Anything can +be done to them, from processing it with Jinja to arbitrary python processing. +They are dicts. + +Sections +======== +Sections are short segments of RST. They will be in the final spec, but they +are unordered. They typically use a combination of templates + units to +construct bits of RST. + +Skeleton +======== +The skeleton is a single RST file which is passed through a templating system to +replace variable names with sections. + +Processing +========== +- Execute all unit functions to load units into memory and process them. +- Execute all section functions (which can now be done because the units exist) +- Execute the skeleton function to bring it into a single file. + +Checks +====== +- Any units made which were not used at least once will produce a warning. +- Any sections made but not used in the skeleton will produce a warning. +""" + from jinja2 import Environment, FileSystemLoader +import internal.units +import internal.sections import json -def jsonify(input): - return json.dumps(input, indent=4) +def load_units(): + print "Loading units..." + return internal.units.load() + +def load_sections(env, units): + print "Loading sections..." + return internal.sections.load(env, units) + +def create_from_skeleton(skeleton, sections): + print "Creating spec from skeleton..." + +def check_unaccessed(name, store): + unaccessed_keys = store.get_unaccessed_set() + if len(unaccessed_keys) > 0: + print "Found %s unused %s keys." % (len(unaccessed_keys), name) + print unaccessed_keys + +def main(): + # add a template filter to produce pretty pretty JSON + def jsonify(input): + return json.dumps(input, indent=4) + + # make Jinja aware of the templates and filters + env = Environment(loader=FileSystemLoader("templates")) + env.filters["jsonify"] = jsonify + + # load up and parse the lowest single units possible: we don't know or care + # which spec section will use it, we just need it there in memory for when + # they want it. + units = load_units() + + # use the units to create RST sections + sections = load_sections(env, units) + + # combine all the RST sections into a coherent spec + skeleton = "foo" + spec = create_from_skeleton(skeleton, sections) -env = Environment(loader=FileSystemLoader("templates")) -env.filters["jsonify"] = jsonify + check_unaccessed("units", units) + check_unaccessed("sections", sections) + print spec -example = {} -with open("../example.json", "r") as f: - example = json.loads(f.read()) -event = {} -with open("../event_schema.json", "r") as f: - event = json.loads(f.read()) -template = env.get_template("events.tmpl") -print template.render(example=example, event=event) +if __name__ == '__main__': + main() diff --git a/templating/internal/__init__.py b/templating/internal/__init__.py new file mode 100644 index 00000000..e14e1ecd --- /dev/null +++ b/templating/internal/__init__.py @@ -0,0 +1,21 @@ +from sets import Set + + +class AccessKeyStore(object): + """Storage for arbitrary data. Monitors get calls so we know if they + were used or not.""" + + def __init__(self): + self.data = {} + self.accessed_set = Set() + + def add(self, key, unit_dict): + self.data[key] = unit_dict + + def get(self, key): + self.accessed_set.add(key) + return self.data[key] + + def get_unaccessed_set(self): + data_list = Set(self.data.keys()) + return data_list - self.accessed_set \ No newline at end of file diff --git a/templating/internal/sections.py b/templating/internal/sections.py new file mode 100644 index 00000000..758e0b6f --- /dev/null +++ b/templating/internal/sections.py @@ -0,0 +1,18 @@ +"""Contains all the sections for the spec.""" +from . import AccessKeyStore +import os + +def _render_section_room_events(env, units): + template = env.get_template("events.tmpl") + return template.render(example=example, event=event) + +SECTION_DICT = { + "room-events": _render_section_room_events +} + +def load(env, units): + store = AccessKeyStore() + for unit_key in SECTION_DICT: + unit = SECTION_DICT[unit_key](env, units) + store.add(unit_key, unit) + return store \ No newline at end of file diff --git a/templating/internal/units.py b/templating/internal/units.py new file mode 100644 index 00000000..cae113ff --- /dev/null +++ b/templating/internal/units.py @@ -0,0 +1,24 @@ +"""Contains all the units for the spec.""" +from . import AccessKeyStore +import os + +def _load_examples(): + path = "../event-schemas/examples" + examples = {} + for filename in os.listdir(path): + with open(filename, "r") as f: + print filename + return examples + + +UNIT_DICT = { + "event-examples": _load_examples +} + + +def load(): + store = AccessKeyStore() + for unit_key in UNIT_DICT: + unit = UNIT_DICT[unit_key]() + store.add(unit_key, unit) + return store From 7563f1058b49a8bfaa37fc4489b3c8c35e86c3a7 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 15:03:31 +0100 Subject: [PATCH 03/13] Make the templating system work(!) --- .gitignore | 1 + templating/build.py | 9 ++- templating/internal/__init__.py | 3 + templating/internal/sections.py | 21 +++++-- templating/internal/units.py | 92 ++++++++++++++++++++++++++++++- templating/templates/events.tmpl | 4 +- templating/templates/skeleton.rst | 6 ++ 7 files changed, 124 insertions(+), 12 deletions(-) create mode 100644 templating/templates/skeleton.rst diff --git a/.gitignore b/.gitignore index c838ab29..b362f22c 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ scripts/gen +*.pyc diff --git a/templating/build.py b/templating/build.py index 4510fa65..14941cb0 100755 --- a/templating/build.py +++ b/templating/build.py @@ -55,6 +55,9 @@ def load_sections(env, units): def create_from_skeleton(skeleton, sections): print "Creating spec from skeleton..." + print "Section keys: %s" % (sections.keys()) + return skeleton.render(sections.data) + def check_unaccessed(name, store): unaccessed_keys = store.get_unaccessed_set() @@ -80,12 +83,14 @@ def main(): sections = load_sections(env, units) # combine all the RST sections into a coherent spec - skeleton = "foo" + skeleton = env.get_template("skeleton.rst") spec = create_from_skeleton(skeleton, sections) check_unaccessed("units", units) check_unaccessed("sections", sections) - print spec + + with open("spec.rst", "w") as f: + f.write(spec) if __name__ == '__main__': diff --git a/templating/internal/__init__.py b/templating/internal/__init__.py index e14e1ecd..7a9c7a22 100644 --- a/templating/internal/__init__.py +++ b/templating/internal/__init__.py @@ -9,6 +9,9 @@ class AccessKeyStore(object): self.data = {} self.accessed_set = Set() + def keys(self): + return self.data.keys() + def add(self, key, unit_dict): self.data[key] = unit_dict diff --git a/templating/internal/sections.py b/templating/internal/sections.py index 758e0b6f..9a6d3bfc 100644 --- a/templating/internal/sections.py +++ b/templating/internal/sections.py @@ -4,15 +4,26 @@ import os def _render_section_room_events(env, units): template = env.get_template("events.tmpl") - return template.render(example=example, event=event) + examples = units.get("event-examples") + schemas = units.get("event-schemas") + sections = [] + for event_name in schemas: + sections.append(template.render( + example=examples[event_name], + event=schemas[event_name] + )) + return "\n\n".join(sections) SECTION_DICT = { - "room-events": _render_section_room_events + "room_events": _render_section_room_events } def load(env, units): store = AccessKeyStore() - for unit_key in SECTION_DICT: - unit = SECTION_DICT[unit_key](env, units) - store.add(unit_key, unit) + for section_key in SECTION_DICT: + section = SECTION_DICT[section_key](env, units) + print "Generated section '%s' : %s" % ( + section_key, section[:60].replace("\n","") + ) + store.add(section_key, section) return store \ No newline at end of file diff --git a/templating/internal/units.py b/templating/internal/units.py index cae113ff..cdade683 100644 --- a/templating/internal/units.py +++ b/templating/internal/units.py @@ -1,18 +1,101 @@ """Contains all the units for the spec.""" from . import AccessKeyStore +import json import os +def prop(obj, path): + # Helper method to extract nested property values + nested_keys = path.split("/") + val = obj + for key in nested_keys: + val = val.get(key, {}) + return val + def _load_examples(): path = "../event-schemas/examples" examples = {} for filename in os.listdir(path): - with open(filename, "r") as f: - print filename + if not filename.startswith("m."): + continue + with open(os.path.join(path, filename), "r") as f: + examples[filename] = json.loads(f.read()) + if filename == "m.room.message_m.text": + examples["m.room.message"] = examples[filename] return examples +def _load_schemas(): + path = "../event-schemas/schema" + schemata = {} + + def format_for_obj(obj): + obj_type = "<%s>" % obj.get("type") + if obj_type == "": + if obj.get("properties"): + format = {} + for key in obj.get("properties"): + format[key] = format_for_obj(obj.get("properties")[key]) + return format + elif obj.get("additionalProperties"): + return { + "": ( + "<%s>" % obj.get("additionalProperties").get("type") + ) + } + enum_text = "" + # add on enum info + enum = obj.get("enum") + if enum: + if len(enum) > 1: + obj_type = "" + enum_text = " (" + "|".join(enum) + ")" + else: + obj_type = enum[0] + + return obj_type + enum_text + + for filename in os.listdir(path): + if not filename.startswith("m."): + continue + with open(os.path.join(path, filename), "r") as f: + json_schema = json.loads(f.read()) + schema = { + "typeof": None, + "type": None, + "summary": None, + "desc": None, + "json_format": None + } + + # add typeof + base_defs = { + "core#/definitions/room_event": "Room Event", + "core#/definitions/state_event": "State Event" + } + if type(json_schema.get("allOf")) == list: + schema["typeof"] = base_defs.get( + json_schema["allOf"][0].get("$ref") + ) + + # add type + schema["type"] = prop(json_schema, "properties/type/enum")[0] + + # add summary and desc + schema["summary"] = json_schema.get("title") + schema["desc"] = json_schema.get("description") + + # add json_format + content_props = prop(json_schema, "properties/content") + if content_props: + schema["json_format"] = format_for_obj(content_props) + + + schemata[filename] = schema + return schemata + UNIT_DICT = { - "event-examples": _load_examples + "event-examples": _load_examples, + "event-schemas": _load_schemas } @@ -20,5 +103,8 @@ def load(): store = AccessKeyStore() for unit_key in UNIT_DICT: unit = UNIT_DICT[unit_key]() + print "Generated unit '%s' : %s" % ( + unit_key, json.dumps(unit)[:50].replace("\n","") + ) store.add(unit_key, unit) return store diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl index 512f7a2e..fa70e5eb 100644 --- a/templating/templates/events.tmpl +++ b/templating/templates/events.tmpl @@ -1,7 +1,7 @@ {{event.type}} -------------- Summary: {{event.summary}} -Type: {{event.parent}} +Type: {{event.typeof}} Description: {{event.desc}} -JSON Format: {{event.content | jsonify}} +JSON Format: {{event.json_format | jsonify}} Example: {{example.content | jsonify}} diff --git a/templating/templates/skeleton.rst b/templating/templates/skeleton.rst new file mode 100644 index 00000000..1fd2d965 --- /dev/null +++ b/templating/templates/skeleton.rst @@ -0,0 +1,6 @@ +This is the skeleton of the spec. + +{{room_events}} + + +Here a footer thingy. \ No newline at end of file From 3c0b56497793bbd9ddefb5178bdce72e7e88cc89 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 15:24:55 +0100 Subject: [PATCH 04/13] Whine if there are missing variables that the template needs. --- templating/build.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/templating/build.py b/templating/build.py index 14941cb0..78e48b02 100755 --- a/templating/build.py +++ b/templating/build.py @@ -40,7 +40,7 @@ Checks - Any sections made but not used in the skeleton will produce a warning. """ -from jinja2 import Environment, FileSystemLoader +from jinja2 import Environment, FileSystemLoader, StrictUndefined import internal.units import internal.sections import json @@ -50,15 +50,14 @@ def load_units(): return internal.units.load() def load_sections(env, units): - print "Loading sections..." + print "\nLoading sections..." return internal.sections.load(env, units) def create_from_skeleton(skeleton, sections): - print "Creating spec from skeleton..." + print "\nCreating spec from skeleton..." print "Section keys: %s" % (sections.keys()) return skeleton.render(sections.data) - def check_unaccessed(name, store): unaccessed_keys = store.get_unaccessed_set() if len(unaccessed_keys) > 0: @@ -71,7 +70,10 @@ def main(): return json.dumps(input, indent=4) # make Jinja aware of the templates and filters - env = Environment(loader=FileSystemLoader("templates")) + env = Environment( + loader=FileSystemLoader("templates"), + undefined=StrictUndefined + ) env.filters["jsonify"] = jsonify # load up and parse the lowest single units possible: we don't know or care @@ -87,7 +89,6 @@ def main(): spec = create_from_skeleton(skeleton, sections) check_unaccessed("units", units) - check_unaccessed("sections", sections) with open("spec.rst", "w") as f: f.write(spec) From 8643b4aaddb2e522526f065c29bed3f80ea1bc2f Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 15:36:44 +0100 Subject: [PATCH 05/13] Update v1 paths, recursively print JSON format for arrays as well as objects. --- templating/internal/units.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/templating/internal/units.py b/templating/internal/units.py index cdade683..aff48a2b 100644 --- a/templating/internal/units.py +++ b/templating/internal/units.py @@ -12,7 +12,7 @@ def prop(obj, path): return val def _load_examples(): - path = "../event-schemas/examples" + path = "../event-schemas/examples/v1" examples = {} for filename in os.listdir(path): if not filename.startswith("m."): @@ -24,7 +24,7 @@ def _load_examples(): return examples def _load_schemas(): - path = "../event-schemas/schema" + path = "../event-schemas/schema/v1" schemata = {} def format_for_obj(obj): @@ -41,6 +41,12 @@ def _load_schemas(): "<%s>" % obj.get("additionalProperties").get("type") ) } + elif obj_type == "" and obj.get("items"): + return [ + format_for_obj(obj.get("items")) + ] + + enum_text = "" # add on enum info enum = obj.get("enum") From c237c9010af41179c203485505f80ba8e5a66bef Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 15:54:24 +0100 Subject: [PATCH 06/13] Actually produce valid RST from the template --- templating/build.py | 8 ++++++-- templating/templates/events.tmpl | 22 +++++++++++++++------- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/templating/build.py b/templating/build.py index 78e48b02..7752f309 100755 --- a/templating/build.py +++ b/templating/build.py @@ -66,8 +66,12 @@ def check_unaccessed(name, store): def main(): # add a template filter to produce pretty pretty JSON - def jsonify(input): - return json.dumps(input, indent=4) + def jsonify(input, pre_whitespace=0): + code = json.dumps(input, indent=4) + if pre_whitespace: + code = code.replace("\n", ("\n" +" "*pre_whitespace)) + + return code # make Jinja aware of the templates and filters env = Environment( diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl index fa70e5eb..602aa108 100644 --- a/templating/templates/events.tmpl +++ b/templating/templates/events.tmpl @@ -1,7 +1,15 @@ -{{event.type}} --------------- -Summary: {{event.summary}} -Type: {{event.typeof}} -Description: {{event.desc}} -JSON Format: {{event.json_format | jsonify}} -Example: {{example.content | jsonify}} +``{{event.type}}`` + Summary: + {{event.summary}} + Type: + {{event.typeof}} + Description: + {{event.desc}} + + JSON Format:: + + {{event.json_format | jsonify(4)}} + + Example:: + + {{example.content | jsonify(4)}} From c61223fb277069d8bc301cc4c4a75e29d0a3e8d9 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 16:49:20 +0100 Subject: [PATCH 07/13] Add title/desc to lots of event schemas, add missing keys (e.g. avatar_url, displayname), add required keys section to spec. --- event-schemas/examples/v1/m.room.member | 4 +++- event-schemas/examples/v1/m.room.redaction | 4 +++- event-schemas/schema/v1/m.room.aliases | 12 ++++++++++++ event-schemas/schema/v1/m.room.create | 3 +++ event-schemas/schema/v1/m.room.join_rules | 5 +++++ event-schemas/schema/v1/m.room.member | 13 +++++++++++++ event-schemas/schema/v1/m.room.message | 6 ++++++ event-schemas/schema/v1/m.room.message.feedback | 7 +++++++ event-schemas/schema/v1/m.room.name | 7 +++++++ event-schemas/schema/v1/m.room.power_levels | 16 +++++++++++++++- event-schemas/schema/v1/m.room.redaction | 9 +++++++++ event-schemas/schema/v1/m.room.topic | 6 ++++++ templating/build.py | 4 ++-- templating/internal/sections.py | 2 ++ templating/internal/units.py | 12 ++++++++++-- templating/templates/events.tmpl | 6 ++++-- 16 files changed, 107 insertions(+), 9 deletions(-) diff --git a/event-schemas/examples/v1/m.room.member b/event-schemas/examples/v1/m.room.member index abfd89ce..b9cd2671 100644 --- a/event-schemas/examples/v1/m.room.member +++ b/event-schemas/examples/v1/m.room.member @@ -1,7 +1,9 @@ { "age": 242352, "content": { - "membership": "join" + "membership": "join", + "avatar_url": "mxc://localhost/SEsfnsuifSDFSSEF#auto", + "displayname": "Alice Margatroid" }, "state_key": "@alice:localhost", "origin_server_ts": 1431961217939, diff --git a/event-schemas/examples/v1/m.room.redaction b/event-schemas/examples/v1/m.room.redaction index dd56e97c..2e8260ea 100644 --- a/event-schemas/examples/v1/m.room.redaction +++ b/event-schemas/examples/v1/m.room.redaction @@ -1,6 +1,8 @@ { "age": 242352, - "content": {}, + "content": { + "reason": "Spamming" + }, "origin_server_ts": 1431961217939, "event_id": "$WLGTSEFSEF:localhost", "type": "m.room.redaction", diff --git a/event-schemas/schema/v1/m.room.aliases b/event-schemas/schema/v1/m.room.aliases index 6c91eed7..25f62876 100644 --- a/event-schemas/schema/v1/m.room.aliases +++ b/event-schemas/schema/v1/m.room.aliases @@ -1,6 +1,18 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "Informs the room about what room aliases it has been given.", + "description": "This event is sent by a homeserver directly to inform of changes + to the list of aliases it knows about for that room. The + ``state_key`` for this event is set to the homeserver which + owns the room alias. The entire set of known aliases for the + room is the union of all the ``m.room.aliases`` events, one + for each homeserver. Clients **should** check the validity + of any room alias given in this list before presenting it to + the user as trusted fact. The lists given by this event + should be considered simply as advice on which aliases + might exist, for which the client can perform the lookup to + confirm whether it receives the correct room ID.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.create b/event-schemas/schema/v1/m.room.create index 9e6e83bd..bfb4a6a9 100644 --- a/event-schemas/schema/v1/m.room.create +++ b/event-schemas/schema/v1/m.room.create @@ -1,6 +1,9 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "The first event in the room.", + "description": "This is the first event in a room and cannot be changed. It acts + as the root of all other events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.join_rules b/event-schemas/schema/v1/m.room.join_rules index 3954a962..a9c6eb58 100644 --- a/event-schemas/schema/v1/m.room.join_rules +++ b/event-schemas/schema/v1/m.room.join_rules @@ -1,6 +1,11 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "Describes how users are allowed to join the room.", + "description": "A room may be ``public`` meaning anyone can join the room without + any prior action. Alternatively, it can be ``invite`` meaning that + a user who wishes to join the room must first receive an invite to + the room from someone already inside of the room.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member index 76b4c360..71b1fe67 100644 --- a/event-schemas/schema/v1/m.room.member +++ b/event-schemas/schema/v1/m.room.member @@ -1,6 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "The current membership state of a user in the room.", + "description": "Adjusts the membership state for a user in a room. It is preferable to use + the membership APIs (``/rooms//invite`` etc) when performing + membership actions rather than adjusting the state directly as there are a + restricted set of valid transformations. For example, user A cannot force + user B to join a room, and trying to force this state change directly will + fail.", "allOf": [{ "$ref": "core#/definitions/state_event" }], @@ -11,6 +18,12 @@ "membership": { "type": "string", "enum": ["invite","join","knock","leave","ban"] + }, + "avatar_url": { + "type": "string" + }, + "displayname": { + "type": "string" } }, "required": ["membership"] diff --git a/event-schemas/schema/v1/m.room.message b/event-schemas/schema/v1/m.room.message index 2b00dfd0..42dc7439 100644 --- a/event-schemas/schema/v1/m.room.message +++ b/event-schemas/schema/v1/m.room.message @@ -1,6 +1,12 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "A human-readable message in the room.", + "description": "This event is used when sending messages in a room. Messages are + not limited to be text. The ``msgtype`` key outlines the type + of message, e.g. text, audio, image, video, etc. The ``body`` key + is text and MUST be used with every kind of ``msgtype`` as a + fallback mechanism for when a client cannot render a message.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.message.feedback b/event-schemas/schema/v1/m.room.message.feedback index 392e630d..5784d9fd 100644 --- a/event-schemas/schema/v1/m.room.message.feedback +++ b/event-schemas/schema/v1/m.room.message.feedback @@ -1,6 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "A receipt for a message. N.B. not implemented in Synapse, and superceded in + v2 CS API by the ``relates_to`` event field.", + "description": "Feedback events are events sent to acknowledge a message in some way. There + are two supported acknowledgements: ``delivered`` (sent when the event has + been received) and ``read`` (sent when the event has been observed by the + end-user). The ``target_event_id`` should reference the ``m.room.message`` + event being acknowledged.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.name b/event-schemas/schema/v1/m.room.name index 6dca3b14..56fdca9a 100644 --- a/event-schemas/schema/v1/m.room.name +++ b/event-schemas/schema/v1/m.room.name @@ -1,5 +1,12 @@ { "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Set the human-readable name for the room.", + "description": "A room has an opaque room ID which is not human-friendly to read. A room + alias is human-friendly, but not all rooms have room aliases. The room + name is a human-friendly string designed to be displayed to the end-user. + The room name is not unique, as multiple rooms can have the same room name + set. The room name can also be set when creating a room using ``/createRoom`` + with the ``name`` key.", "type": "object", "allOf": [{ "$ref": "core#/definitions/state_event" diff --git a/event-schemas/schema/v1/m.room.power_levels b/event-schemas/schema/v1/m.room.power_levels index 91621149..4104bab7 100644 --- a/event-schemas/schema/v1/m.room.power_levels +++ b/event-schemas/schema/v1/m.room.power_levels @@ -1,6 +1,20 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "Defines the power levels (privileges) of users in the room.", + "description": "This event specifies the minimum level a user must have in order + to perform a certain action. It also specifies the levels + of each user in the room. If a ``user_id`` is in the + ``users`` list, then that ``user_id`` has the associated + power level. Otherwise they have the default level + ``users_default``. If ``users_default`` is not supplied, + it is assumed to be 0. The level required to send a + certain event is governed by ``events``, ``state_default`` + and ``events_default``. If an event type is specified in + ``events``, then the user must have at least the level + specified in order to send that event. If the event type is + not supplied, it defaults to ``events_default`` for Message + Events and ``state_default`` for State Events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], @@ -28,7 +42,7 @@ } }, "required": ["ban","events","events_default","kick","redact", - "state_default","users","users_default"] + "state_default","users"] }, "state_key": { "type": "string", diff --git a/event-schemas/schema/v1/m.room.redaction b/event-schemas/schema/v1/m.room.redaction index 5b5dd6db..910e3215 100644 --- a/event-schemas/schema/v1/m.room.redaction +++ b/event-schemas/schema/v1/m.room.redaction @@ -1,6 +1,15 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "Indicates a previous event has been redacted.", + "description": "Events can be redacted by either room or server admins. Redacting an event + means that all keys not required by the protocol are stripped off, allowing + admins to remove offensive or illegal content that may have been attached + to any event. This cannot be undone, allowing server owners to physically + delete the offending data. There is also a concept of a moderator hiding a + message event, which can be undone, but cannot be applied to state + events. The event that has been redacted is specified in the ``redacts`` + event level key.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.topic b/event-schemas/schema/v1/m.room.topic index 80ae1191..86f4c97b 100644 --- a/event-schemas/schema/v1/m.room.topic +++ b/event-schemas/schema/v1/m.room.topic @@ -1,6 +1,12 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", + "title": "Set a topic for the room.", + "description": "A topic is a short message detailing what is currently being discussed in + the room. It can also be used as a way to display extra information about + the room, which may not be suitable for the room name. The room topic can + also be set when creating a room using ``/createRoom`` with the ``topic`` + key.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/templating/build.py b/templating/build.py index 7752f309..ebf4ac18 100755 --- a/templating/build.py +++ b/templating/build.py @@ -66,8 +66,8 @@ def check_unaccessed(name, store): def main(): # add a template filter to produce pretty pretty JSON - def jsonify(input, pre_whitespace=0): - code = json.dumps(input, indent=4) + def jsonify(input, indent=None, pre_whitespace=0): + code = json.dumps(input, indent=indent) if pre_whitespace: code = code.replace("\n", ("\n" +" "*pre_whitespace)) diff --git a/templating/internal/sections.py b/templating/internal/sections.py index 9a6d3bfc..ad408642 100644 --- a/templating/internal/sections.py +++ b/templating/internal/sections.py @@ -8,6 +8,8 @@ def _render_section_room_events(env, units): schemas = units.get("event-schemas") sections = [] for event_name in schemas: + if not event_name.startswith("m.room"): + continue sections.append(template.render( example=examples[event_name], event=schemas[event_name] diff --git a/templating/internal/units.py b/templating/internal/units.py index aff48a2b..be669ea7 100644 --- a/templating/internal/units.py +++ b/templating/internal/units.py @@ -62,14 +62,16 @@ def _load_schemas(): for filename in os.listdir(path): if not filename.startswith("m."): continue + print "Reading %s" % os.path.join(path, filename) with open(os.path.join(path, filename), "r") as f: - json_schema = json.loads(f.read()) + json_schema = json.loads(f.read(), strict=False) schema = { "typeof": None, "type": None, "summary": None, "desc": None, - "json_format": None + "json_format": None, + "required_keys": None } # add typeof @@ -94,6 +96,12 @@ def _load_schemas(): if content_props: schema["json_format"] = format_for_obj(content_props) + # add required_keys + schema["required_keys"] = prop( + json_schema, "properties/content/required" + ) + + schemata[filename] = schema return schemata diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl index 602aa108..7520701c 100644 --- a/templating/templates/events.tmpl +++ b/templating/templates/events.tmpl @@ -5,11 +5,13 @@ {{event.typeof}} Description: {{event.desc}} + Required Keys: + ``{{event.required_keys | jsonify}}`` JSON Format:: - {{event.json_format | jsonify(4)}} + {{event.json_format | jsonify(4, 4)}} Example:: - {{example.content | jsonify(4)}} + {{example.content | jsonify(4, 4)}} From 1a65c051e8984d5e58847b0c8fff11183fdc90af Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Tue, 19 May 2015 17:05:34 +0100 Subject: [PATCH 08/13] Whitespace --- templating/internal/units.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/templating/internal/units.py b/templating/internal/units.py index be669ea7..5d387c99 100644 --- a/templating/internal/units.py +++ b/templating/internal/units.py @@ -46,7 +46,6 @@ def _load_schemas(): format_for_obj(obj.get("items")) ] - enum_text = "" # add on enum info enum = obj.get("enum") @@ -100,19 +99,14 @@ def _load_schemas(): schema["required_keys"] = prop( json_schema, "properties/content/required" ) - - - schemata[filename] = schema return schemata - UNIT_DICT = { "event-examples": _load_examples, "event-schemas": _load_schemas } - def load(): store = AccessKeyStore() for unit_key in UNIT_DICT: From 2e5809a68ee8ba664e6a454b3c2aac05e791735a Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 20 May 2015 11:19:32 +0100 Subject: [PATCH 09/13] Make build.py accept generic files for template var substitutions. This allows us to incrementally convert sections of the spec to use this templating system. E.g. './build.py ../specification/20_events.rst' where that .rst file has {{room_events}} in it somewhere. Add ability to show a list of valid template vars to use (e.g. room_events) by running './build.py --show-template-vars'. --- templating/build.py | 72 +++++++++++++++++++++++++------ templating/templates/skeleton.rst | 6 --- 2 files changed, 58 insertions(+), 20 deletions(-) delete mode 100644 templating/templates/skeleton.rst diff --git a/templating/build.py b/templating/build.py index ebf4ac18..4de25791 100755 --- a/templating/build.py +++ b/templating/build.py @@ -40,10 +40,14 @@ Checks - Any sections made but not used in the skeleton will produce a warning. """ -from jinja2 import Environment, FileSystemLoader, StrictUndefined +from jinja2 import Environment, FileSystemLoader, StrictUndefined, Template +from argparse import ArgumentParser, FileType +import json +import os +import sys + import internal.units import internal.sections -import json def load_units(): print "Loading units..." @@ -53,10 +57,8 @@ def load_sections(env, units): print "\nLoading sections..." return internal.sections.load(env, units) -def create_from_skeleton(skeleton, sections): - print "\nCreating spec from skeleton..." - print "Section keys: %s" % (sections.keys()) - return skeleton.render(sections.data) +def create_from_template(template, sections): + return template.render(sections.data) def check_unaccessed(name, store): unaccessed_keys = store.get_unaccessed_set() @@ -64,7 +66,10 @@ def check_unaccessed(name, store): print "Found %s unused %s keys." % (len(unaccessed_keys), name) print unaccessed_keys -def main(): +def main(file_stream=None, out_dir=None): + if out_dir and not os.path.exists(out_dir): + os.makedirs(out_dir) + # add a template filter to produce pretty pretty JSON def jsonify(input, indent=None, pre_whitespace=0): code = json.dumps(input, indent=indent) @@ -88,15 +93,54 @@ def main(): # use the units to create RST sections sections = load_sections(env, units) - # combine all the RST sections into a coherent spec - skeleton = env.get_template("skeleton.rst") - spec = create_from_skeleton(skeleton, sections) + # print out valid section keys if no file supplied + if not file_stream: + print "\nValid template variables:" + for key in sections.keys(): + print " %s" % key + return + + # check the input files and substitute in sections where required + print "Parsing input template: %s" % file_stream.name + temp = Template(file_stream.read()) + print "Creating output for: %s" % file_stream.name + output = create_from_template(temp, sections) + with open(os.path.join(out_dir, file_stream.name), "w") as f: + f.write(output) + print "Output file for: %s" % file_stream.name check_unaccessed("units", units) - - with open("spec.rst", "w") as f: - f.write(spec) if __name__ == '__main__': - main() + parser = ArgumentParser( + "Process a file (typically .rst) and replace templated areas with spec"+ + " info. For a list of possible template variables, add"+ + " --show-template-vars." + ) + parser.add_argument( + "file", nargs="?", type=FileType('r'), + help="The input file to process." + ) + parser.add_argument( + "--out-directory", "-o", help="The directory to output the file to."+ + " Default: /out", + default="out" + ) + parser.add_argument( + "--show-template-vars", "-s", action="store_true", + help="Show a list of all possible variables you can use in the"+ + " input file." + ) + args = parser.parse_args() + + if (args.show_template_vars): + main() + sys.exit(0) + + if not args.file: + print "No file supplied." + parser.print_help() + sys.exit(1) + + main(file_stream=args.file, out_dir=args.out_directory) diff --git a/templating/templates/skeleton.rst b/templating/templates/skeleton.rst deleted file mode 100644 index 1fd2d965..00000000 --- a/templating/templates/skeleton.rst +++ /dev/null @@ -1,6 +0,0 @@ -This is the skeleton of the spec. - -{{room_events}} - - -Here a footer thingy. \ No newline at end of file From 8f448a176415430203a22783043b73b53c2c81e0 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 20 May 2015 11:49:10 +0100 Subject: [PATCH 10/13] Produce valid JSON (escape \n), add indent filter --- event-schemas/schema/v1/m.room.aliases | 12 +----------- event-schemas/schema/v1/m.room.create | 3 +-- event-schemas/schema/v1/m.room.join_rules | 5 +---- event-schemas/schema/v1/m.room.member | 7 +------ event-schemas/schema/v1/m.room.message | 6 +----- event-schemas/schema/v1/m.room.message.feedback | 9 ++------- event-schemas/schema/v1/m.room.name | 7 +------ event-schemas/schema/v1/m.room.power_levels | 14 +------------- event-schemas/schema/v1/m.room.redaction | 9 +-------- event-schemas/schema/v1/m.room.topic | 6 +----- templating/build.py | 4 ++++ templating/internal/units.py | 2 +- templating/templates/events.tmpl | 2 +- 13 files changed, 17 insertions(+), 69 deletions(-) diff --git a/event-schemas/schema/v1/m.room.aliases b/event-schemas/schema/v1/m.room.aliases index 25f62876..1c86183f 100644 --- a/event-schemas/schema/v1/m.room.aliases +++ b/event-schemas/schema/v1/m.room.aliases @@ -2,17 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Informs the room about what room aliases it has been given.", - "description": "This event is sent by a homeserver directly to inform of changes - to the list of aliases it knows about for that room. The - ``state_key`` for this event is set to the homeserver which - owns the room alias. The entire set of known aliases for the - room is the union of all the ``m.room.aliases`` events, one - for each homeserver. Clients **should** check the validity - of any room alias given in this list before presenting it to - the user as trusted fact. The lists given by this event - should be considered simply as advice on which aliases - might exist, for which the client can perform the lookup to - confirm whether it receives the correct room ID.", + "description": "This event is sent by a homeserver directly to inform of changes\nto the list of aliases it knows about for that room. The\n``state_key`` for this event is set to the homeserver which\nowns the room alias. The entire set of known aliases for the\nroom is the union of all the ``m.room.aliases`` events, one\nfor each homeserver. Clients **should** check the validity\nof any room alias given in this list before presenting it to\nthe user as trusted fact. The lists given by this event\nshould be considered simply as advice on which aliases\nmight exist, for which the client can perform the lookup to\nconfirm whether it receives the correct room ID.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.create b/event-schemas/schema/v1/m.room.create index bfb4a6a9..c1cd1a39 100644 --- a/event-schemas/schema/v1/m.room.create +++ b/event-schemas/schema/v1/m.room.create @@ -2,8 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "The first event in the room.", - "description": "This is the first event in a room and cannot be changed. It acts - as the root of all other events.", + "description": "This is the first event in a room and cannot be changed. It acts\nas the root of all other events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.join_rules b/event-schemas/schema/v1/m.room.join_rules index a9c6eb58..ec430ff7 100644 --- a/event-schemas/schema/v1/m.room.join_rules +++ b/event-schemas/schema/v1/m.room.join_rules @@ -2,10 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Describes how users are allowed to join the room.", - "description": "A room may be ``public`` meaning anyone can join the room without - any prior action. Alternatively, it can be ``invite`` meaning that - a user who wishes to join the room must first receive an invite to - the room from someone already inside of the room.", + "description": "A room may be ``public`` meaning anyone can join the room without\nany prior action. Alternatively, it can be ``invite`` meaning that\na user who wishes to join the room must first receive an invite to\nthe room from someone already inside of the room.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member index 71b1fe67..22be8218 100644 --- a/event-schemas/schema/v1/m.room.member +++ b/event-schemas/schema/v1/m.room.member @@ -2,12 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "The current membership state of a user in the room.", - "description": "Adjusts the membership state for a user in a room. It is preferable to use - the membership APIs (``/rooms//invite`` etc) when performing - membership actions rather than adjusting the state directly as there are a - restricted set of valid transformations. For example, user A cannot force - user B to join a room, and trying to force this state change directly will - fail.", + "description": "Adjusts the membership state for a user in a room. It is preferable to use\nthe membership APIs (``/rooms//invite`` etc) when performing\nmembership actions rather than adjusting the state directly as there are a\nrestricted set of valid transformations. For example, user A cannot force\nuser B to join a room, and trying to force this state change directly will\nfail.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.message b/event-schemas/schema/v1/m.room.message index 42dc7439..58e929e6 100644 --- a/event-schemas/schema/v1/m.room.message +++ b/event-schemas/schema/v1/m.room.message @@ -2,11 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "A human-readable message in the room.", - "description": "This event is used when sending messages in a room. Messages are - not limited to be text. The ``msgtype`` key outlines the type - of message, e.g. text, audio, image, video, etc. The ``body`` key - is text and MUST be used with every kind of ``msgtype`` as a - fallback mechanism for when a client cannot render a message.", + "description": "This event is used when sending messages in a room. Messages are\nnot limited to be text. The ``msgtype`` key outlines the type\nof message, e.g. text, audio, image, video, etc. The ``body`` key\nis text and MUST be used with every kind of ``msgtype`` as a \nfallback mechanism for when a client cannot render a message.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.message.feedback b/event-schemas/schema/v1/m.room.message.feedback index 5784d9fd..062581e7 100644 --- a/event-schemas/schema/v1/m.room.message.feedback +++ b/event-schemas/schema/v1/m.room.message.feedback @@ -1,13 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "title": "A receipt for a message. N.B. not implemented in Synapse, and superceded in - v2 CS API by the ``relates_to`` event field.", - "description": "Feedback events are events sent to acknowledge a message in some way. There - are two supported acknowledgements: ``delivered`` (sent when the event has - been received) and ``read`` (sent when the event has been observed by the - end-user). The ``target_event_id`` should reference the ``m.room.message`` - event being acknowledged.", + "title": "A receipt for a message. N.B. not implemented in Synapse, and superceded in\nv2 CS API by the ``relates_to`` event field.", + "description": "Feedback events are events sent to acknowledge a message in some way. There\nare two supported acknowledgements: ``delivered`` (sent when the event has\nbeen received) and ``read`` (sent when the event has been observed by the\nend-user). The ``target_event_id`` should reference the ``m.room.message``\nevent being acknowledged.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.name b/event-schemas/schema/v1/m.room.name index 56fdca9a..cc27c84b 100644 --- a/event-schemas/schema/v1/m.room.name +++ b/event-schemas/schema/v1/m.room.name @@ -1,12 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Set the human-readable name for the room.", - "description": "A room has an opaque room ID which is not human-friendly to read. A room - alias is human-friendly, but not all rooms have room aliases. The room - name is a human-friendly string designed to be displayed to the end-user. - The room name is not unique, as multiple rooms can have the same room name - set. The room name can also be set when creating a room using ``/createRoom`` - with the ``name`` key.", + "description": "A room has an opaque room ID which is not human-friendly to read. A room\nalias is human-friendly, but not all rooms have room aliases. The room \nname is a human-friendly string designed to be displayed to the end-user.\nThe room name is not unique, as multiple rooms can have the same room name\nset. The room name can also be set when creating a room using ``/createRoom`` \nwith the ``name`` key.", "type": "object", "allOf": [{ "$ref": "core#/definitions/state_event" diff --git a/event-schemas/schema/v1/m.room.power_levels b/event-schemas/schema/v1/m.room.power_levels index 4104bab7..0e8e3946 100644 --- a/event-schemas/schema/v1/m.room.power_levels +++ b/event-schemas/schema/v1/m.room.power_levels @@ -2,19 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Defines the power levels (privileges) of users in the room.", - "description": "This event specifies the minimum level a user must have in order - to perform a certain action. It also specifies the levels - of each user in the room. If a ``user_id`` is in the - ``users`` list, then that ``user_id`` has the associated - power level. Otherwise they have the default level - ``users_default``. If ``users_default`` is not supplied, - it is assumed to be 0. The level required to send a - certain event is governed by ``events``, ``state_default`` - and ``events_default``. If an event type is specified in - ``events``, then the user must have at least the level - specified in order to send that event. If the event type is - not supplied, it defaults to ``events_default`` for Message - Events and ``state_default`` for State Events.", + "description": "This event specifies the minimum level a user must have in order\nto perform a certain action. It also specifies the levels\nof each user in the room. If a ``user_id`` is in the\n``users`` list, then that ``user_id`` has the associated\npower level. Otherwise they have the default level\n``users_default``. If ``users_default`` is not supplied,\nit is assumed to be 0. The level required to send a\ncertain event is governed by ``events``, ``state_default``\nand ``events_default``. If an event type is specified in \n``events``, then the user must have at least the level\nspecified in order to send that event. If the event type is\nnot supplied, it defaults to ``events_default`` for Message\nEvents and ``state_default`` for State Events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.redaction b/event-schemas/schema/v1/m.room.redaction index 910e3215..f036500b 100644 --- a/event-schemas/schema/v1/m.room.redaction +++ b/event-schemas/schema/v1/m.room.redaction @@ -2,14 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Indicates a previous event has been redacted.", - "description": "Events can be redacted by either room or server admins. Redacting an event - means that all keys not required by the protocol are stripped off, allowing - admins to remove offensive or illegal content that may have been attached - to any event. This cannot be undone, allowing server owners to physically - delete the offending data. There is also a concept of a moderator hiding a - message event, which can be undone, but cannot be applied to state - events. The event that has been redacted is specified in the ``redacts`` - event level key.", + "description": "Events can be redacted by either room or server admins. Redacting an event\nmeans that all keys not required by the protocol are stripped off, allowing\nadmins to remove offensive or illegal content that may have been attached\nto any event. This cannot be undone, allowing server owners to physically\ndelete the offending data. There is also a concept of a moderator hiding a\nmessage event, which can be undone, but cannot be applied to state\nevents. The event that has been redacted is specified in the ``redacts``\nevent level key.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.topic b/event-schemas/schema/v1/m.room.topic index 86f4c97b..5847fa65 100644 --- a/event-schemas/schema/v1/m.room.topic +++ b/event-schemas/schema/v1/m.room.topic @@ -2,11 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Set a topic for the room.", - "description": "A topic is a short message detailing what is currently being discussed in - the room. It can also be used as a way to display extra information about - the room, which may not be suitable for the room name. The room topic can - also be set when creating a room using ``/createRoom`` with the ``topic`` - key.", + "description": "A topic is a short message detailing what is currently being discussed in\nthe room. It can also be used as a way to display extra information about\nthe room, which may not be suitable for the room name. The room topic can\nalso be set when creating a room using ``/createRoom`` with the ``topic``\nkey.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/templating/build.py b/templating/build.py index 4de25791..35fde617 100755 --- a/templating/build.py +++ b/templating/build.py @@ -78,12 +78,16 @@ def main(file_stream=None, out_dir=None): return code + def indent(input, indent): + return input.replace("\n", ("\n" + " "*indent)) + # make Jinja aware of the templates and filters env = Environment( loader=FileSystemLoader("templates"), undefined=StrictUndefined ) env.filters["jsonify"] = jsonify + env.filters["indent"] = indent # load up and parse the lowest single units possible: we don't know or care # which spec section will use it, we just need it there in memory for when diff --git a/templating/internal/units.py b/templating/internal/units.py index 5d387c99..a1b90ad4 100644 --- a/templating/internal/units.py +++ b/templating/internal/units.py @@ -63,7 +63,7 @@ def _load_schemas(): continue print "Reading %s" % os.path.join(path, filename) with open(os.path.join(path, filename), "r") as f: - json_schema = json.loads(f.read(), strict=False) + json_schema = json.loads(f.read()) schema = { "typeof": None, "type": None, diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl index 7520701c..80a751e0 100644 --- a/templating/templates/events.tmpl +++ b/templating/templates/events.tmpl @@ -4,7 +4,7 @@ Type: {{event.typeof}} Description: - {{event.desc}} + {{event.desc | indent(4)}} Required Keys: ``{{event.required_keys | jsonify}}`` From 75e7d09fc82258f104d7b938e8ce0722321d47d9 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 20 May 2015 12:41:25 +0100 Subject: [PATCH 11/13] Remove newlines from json: move line wrapping to the template files. --- event-schemas/schema/v1/m.room.aliases | 2 +- event-schemas/schema/v1/m.room.create | 2 +- event-schemas/schema/v1/m.room.join_rules | 2 +- event-schemas/schema/v1/m.room.member | 2 +- event-schemas/schema/v1/m.room.message | 2 +- event-schemas/schema/v1/m.room.message.feedback | 4 ++-- event-schemas/schema/v1/m.room.name | 2 +- event-schemas/schema/v1/m.room.power_levels | 2 +- event-schemas/schema/v1/m.room.redaction | 2 +- event-schemas/schema/v1/m.room.topic | 2 +- templating/build.py | 5 +++++ templating/templates/events.tmpl | 4 ++-- 12 files changed, 18 insertions(+), 13 deletions(-) diff --git a/event-schemas/schema/v1/m.room.aliases b/event-schemas/schema/v1/m.room.aliases index 1c86183f..03842221 100644 --- a/event-schemas/schema/v1/m.room.aliases +++ b/event-schemas/schema/v1/m.room.aliases @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Informs the room about what room aliases it has been given.", - "description": "This event is sent by a homeserver directly to inform of changes\nto the list of aliases it knows about for that room. The\n``state_key`` for this event is set to the homeserver which\nowns the room alias. The entire set of known aliases for the\nroom is the union of all the ``m.room.aliases`` events, one\nfor each homeserver. Clients **should** check the validity\nof any room alias given in this list before presenting it to\nthe user as trusted fact. The lists given by this event\nshould be considered simply as advice on which aliases\nmight exist, for which the client can perform the lookup to\nconfirm whether it receives the correct room ID.", + "description": "This event is sent by a homeserver directly to inform of changes to the list of aliases it knows about for that room. The ``state_key`` for this event is set to the homeserver which owns the room alias. The entire set of known aliases for the room is the union of all the ``m.room.aliases`` events, one for each homeserver. Clients **should** check the validity of any room alias given in this list before presenting it to the user as trusted fact. The lists given by this event should be considered simply as advice on which aliases might exist, for which the client can perform the lookup to confirm whether it receives the correct room ID.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.create b/event-schemas/schema/v1/m.room.create index c1cd1a39..53bb1a45 100644 --- a/event-schemas/schema/v1/m.room.create +++ b/event-schemas/schema/v1/m.room.create @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "The first event in the room.", - "description": "This is the first event in a room and cannot be changed. It acts\nas the root of all other events.", + "description": "This is the first event in a room and cannot be changed. It acts as the root of all other events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.join_rules b/event-schemas/schema/v1/m.room.join_rules index ec430ff7..7c8189b2 100644 --- a/event-schemas/schema/v1/m.room.join_rules +++ b/event-schemas/schema/v1/m.room.join_rules @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Describes how users are allowed to join the room.", - "description": "A room may be ``public`` meaning anyone can join the room without\nany prior action. Alternatively, it can be ``invite`` meaning that\na user who wishes to join the room must first receive an invite to\nthe room from someone already inside of the room.", + "description": "A room may be ``public`` meaning anyone can join the room without any prior action. Alternatively, it can be ``invite`` meaning that a user who wishes to join the room must first receive an invite to the room from someone already inside of the room.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.member b/event-schemas/schema/v1/m.room.member index 22be8218..5cd8d297 100644 --- a/event-schemas/schema/v1/m.room.member +++ b/event-schemas/schema/v1/m.room.member @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "The current membership state of a user in the room.", - "description": "Adjusts the membership state for a user in a room. It is preferable to use\nthe membership APIs (``/rooms//invite`` etc) when performing\nmembership actions rather than adjusting the state directly as there are a\nrestricted set of valid transformations. For example, user A cannot force\nuser B to join a room, and trying to force this state change directly will\nfail.", + "description": "Adjusts the membership state for a user in a room. It is preferable to use the membership APIs (``/rooms//invite`` etc) when performing membership actions rather than adjusting the state directly as there are a restricted set of valid transformations. For example, user A cannot force user B to join a room, and trying to force this state change directly will fail.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.message b/event-schemas/schema/v1/m.room.message index 58e929e6..e5a109f7 100644 --- a/event-schemas/schema/v1/m.room.message +++ b/event-schemas/schema/v1/m.room.message @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "A human-readable message in the room.", - "description": "This event is used when sending messages in a room. Messages are\nnot limited to be text. The ``msgtype`` key outlines the type\nof message, e.g. text, audio, image, video, etc. The ``body`` key\nis text and MUST be used with every kind of ``msgtype`` as a \nfallback mechanism for when a client cannot render a message.", + "description": "This event is used when sending messages in a room. Messages are not limited to be text. The ``msgtype`` key outlines the type of message, e.g. text, audio, image, video, etc. The ``body`` key is text and MUST be used with every kind of ``msgtype`` as a fallback mechanism for when a client cannot render a message.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.message.feedback b/event-schemas/schema/v1/m.room.message.feedback index 062581e7..62c75e32 100644 --- a/event-schemas/schema/v1/m.room.message.feedback +++ b/event-schemas/schema/v1/m.room.message.feedback @@ -1,8 +1,8 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", - "title": "A receipt for a message. N.B. not implemented in Synapse, and superceded in\nv2 CS API by the ``relates_to`` event field.", - "description": "Feedback events are events sent to acknowledge a message in some way. There\nare two supported acknowledgements: ``delivered`` (sent when the event has\nbeen received) and ``read`` (sent when the event has been observed by the\nend-user). The ``target_event_id`` should reference the ``m.room.message``\nevent being acknowledged.", + "title": "A receipt for a message. N.B. not implemented in Synapse, and superceded in v2 CS API by the ``relates_to`` event field.", + "description": "Feedback events are events sent to acknowledge a message in some way. There are two supported acknowledgements: ``delivered`` (sent when the event has been received) and ``read`` (sent when the event has been observed by the end-user). The ``target_event_id`` should reference the ``m.room.message`` event being acknowledged.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.name b/event-schemas/schema/v1/m.room.name index cc27c84b..3d70fa16 100644 --- a/event-schemas/schema/v1/m.room.name +++ b/event-schemas/schema/v1/m.room.name @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/draft-04/schema#", "title": "Set the human-readable name for the room.", - "description": "A room has an opaque room ID which is not human-friendly to read. A room\nalias is human-friendly, but not all rooms have room aliases. The room \nname is a human-friendly string designed to be displayed to the end-user.\nThe room name is not unique, as multiple rooms can have the same room name\nset. The room name can also be set when creating a room using ``/createRoom`` \nwith the ``name`` key.", + "description": "A room has an opaque room ID which is not human-friendly to read. A room alias is human-friendly, but not all rooms have room aliases. The room name is a human-friendly string designed to be displayed to the end-user. The room name is not unique, as multiple rooms can have the same room name set. The room name can also be set when creating a room using ``/createRoom`` with the ``name`` key.", "type": "object", "allOf": [{ "$ref": "core#/definitions/state_event" diff --git a/event-schemas/schema/v1/m.room.power_levels b/event-schemas/schema/v1/m.room.power_levels index 0e8e3946..5db1f961 100644 --- a/event-schemas/schema/v1/m.room.power_levels +++ b/event-schemas/schema/v1/m.room.power_levels @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Defines the power levels (privileges) of users in the room.", - "description": "This event specifies the minimum level a user must have in order\nto perform a certain action. It also specifies the levels\nof each user in the room. If a ``user_id`` is in the\n``users`` list, then that ``user_id`` has the associated\npower level. Otherwise they have the default level\n``users_default``. If ``users_default`` is not supplied,\nit is assumed to be 0. The level required to send a\ncertain event is governed by ``events``, ``state_default``\nand ``events_default``. If an event type is specified in \n``events``, then the user must have at least the level\nspecified in order to send that event. If the event type is\nnot supplied, it defaults to ``events_default`` for Message\nEvents and ``state_default`` for State Events.", + "description": "This event specifies the minimum level a user must have in order to perform a certain action. It also specifies the levels of each user in the room. If a ``user_id`` is in the ``users`` list, then that ``user_id`` has the associated power level. Otherwise they have the default level ``users_default``. If ``users_default`` is not supplied, it is assumed to be 0. The level required to send a certain event is governed by ``events``, ``state_default`` and ``events_default``. If an event type is specified in ``events``, then the user must have at least the level specified in order to send that event. If the event type is not supplied, it defaults to ``events_default`` for Message Events and ``state_default`` for State Events.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/event-schemas/schema/v1/m.room.redaction b/event-schemas/schema/v1/m.room.redaction index f036500b..fb6dd997 100644 --- a/event-schemas/schema/v1/m.room.redaction +++ b/event-schemas/schema/v1/m.room.redaction @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Indicates a previous event has been redacted.", - "description": "Events can be redacted by either room or server admins. Redacting an event\nmeans that all keys not required by the protocol are stripped off, allowing\nadmins to remove offensive or illegal content that may have been attached\nto any event. This cannot be undone, allowing server owners to physically\ndelete the offending data. There is also a concept of a moderator hiding a\nmessage event, which can be undone, but cannot be applied to state\nevents. The event that has been redacted is specified in the ``redacts``\nevent level key.", + "description": "Events can be redacted by either room or server admins. Redacting an event means that all keys not required by the protocol are stripped off, allowing admins to remove offensive or illegal content that may have been attached to any event. This cannot be undone, allowing server owners to physically delete the offending data. There is also a concept of a moderator hiding a message event, which can be undone, but cannot be applied to state events. The event that has been redacted is specified in the ``redacts`` event level key.", "allOf": [{ "$ref": "core#/definitions/room_event" }], diff --git a/event-schemas/schema/v1/m.room.topic b/event-schemas/schema/v1/m.room.topic index 5847fa65..b3fc4574 100644 --- a/event-schemas/schema/v1/m.room.topic +++ b/event-schemas/schema/v1/m.room.topic @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/draft-04/schema#", "type": "object", "title": "Set a topic for the room.", - "description": "A topic is a short message detailing what is currently being discussed in\nthe room. It can also be used as a way to display extra information about\nthe room, which may not be suitable for the room name. The room topic can\nalso be set when creating a room using ``/createRoom`` with the ``topic``\nkey.", + "description": "A topic is a short message detailing what is currently being discussed in the room. It can also be used as a way to display extra information about the room, which may not be suitable for the room name. The room topic can also be set when creating a room using ``/createRoom`` with the ``topic`` key.", "allOf": [{ "$ref": "core#/definitions/state_event" }], diff --git a/templating/build.py b/templating/build.py index 35fde617..40115370 100755 --- a/templating/build.py +++ b/templating/build.py @@ -45,6 +45,7 @@ from argparse import ArgumentParser, FileType import json import os import sys +import textwrap import internal.units import internal.sections @@ -81,6 +82,9 @@ def main(file_stream=None, out_dir=None): def indent(input, indent): return input.replace("\n", ("\n" + " "*indent)) + def wrap(input, wrap=80): + return '\n'.join(textwrap.wrap(input, wrap)) + # make Jinja aware of the templates and filters env = Environment( loader=FileSystemLoader("templates"), @@ -88,6 +92,7 @@ def main(file_stream=None, out_dir=None): ) env.filters["jsonify"] = jsonify env.filters["indent"] = indent + env.filters["wrap"] = wrap # load up and parse the lowest single units possible: we don't know or care # which spec section will use it, we just need it there in memory for when diff --git a/templating/templates/events.tmpl b/templating/templates/events.tmpl index 80a751e0..1bb721b4 100644 --- a/templating/templates/events.tmpl +++ b/templating/templates/events.tmpl @@ -1,10 +1,10 @@ ``{{event.type}}`` Summary: - {{event.summary}} + {{event.summary | wrap(76) | indent(4)}} Type: {{event.typeof}} Description: - {{event.desc | indent(4)}} + {{event.desc | wrap(76) | indent(4)}} Required Keys: ``{{event.required_keys | jsonify}}`` From e62e7b95cd93f89f1e01f927c4547901abf922b8 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 20 May 2015 12:52:52 +0100 Subject: [PATCH 12/13] Rename msgtype examples to have # separator --- .../v1/{m.room.message_m.emote => m.room.message#m.emote} | 0 .../v1/{m.room.message_m.image => m.room.message#m.image} | 0 .../v1/{m.room.message_m.notice => m.room.message#m.notice} | 0 .../examples/v1/{m.room.message_m.text => m.room.message#m.text} | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename event-schemas/examples/v1/{m.room.message_m.emote => m.room.message#m.emote} (100%) rename event-schemas/examples/v1/{m.room.message_m.image => m.room.message#m.image} (100%) rename event-schemas/examples/v1/{m.room.message_m.notice => m.room.message#m.notice} (100%) rename event-schemas/examples/v1/{m.room.message_m.text => m.room.message#m.text} (100%) diff --git a/event-schemas/examples/v1/m.room.message_m.emote b/event-schemas/examples/v1/m.room.message#m.emote similarity index 100% rename from event-schemas/examples/v1/m.room.message_m.emote rename to event-schemas/examples/v1/m.room.message#m.emote diff --git a/event-schemas/examples/v1/m.room.message_m.image b/event-schemas/examples/v1/m.room.message#m.image similarity index 100% rename from event-schemas/examples/v1/m.room.message_m.image rename to event-schemas/examples/v1/m.room.message#m.image diff --git a/event-schemas/examples/v1/m.room.message_m.notice b/event-schemas/examples/v1/m.room.message#m.notice similarity index 100% rename from event-schemas/examples/v1/m.room.message_m.notice rename to event-schemas/examples/v1/m.room.message#m.notice diff --git a/event-schemas/examples/v1/m.room.message_m.text b/event-schemas/examples/v1/m.room.message#m.text similarity index 100% rename from event-schemas/examples/v1/m.room.message_m.text rename to event-schemas/examples/v1/m.room.message#m.text From e8d11e8321fe32494b5a6339b5ee96866b3b0ec2 Mon Sep 17 00:00:00 2001 From: Kegan Dougal Date: Wed, 20 May 2015 13:17:51 +0100 Subject: [PATCH 13/13] Add check.sh to automatically run z-schema over all schema files and test against examples. --- event-schemas/check.sh | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100755 event-schemas/check.sh diff --git a/event-schemas/check.sh b/event-schemas/check.sh new file mode 100755 index 00000000..58604640 --- /dev/null +++ b/event-schemas/check.sh @@ -0,0 +1,19 @@ +#!/bin/bash -e +# Runs z-schema over all of the schema files (looking for matching examples) +find schema/v1/m.* | while read line +do + split_path=(${line///// }) + event_type=(${split_path[2]}) + echo "Checking $event_type" + echo "--------------------" + # match exact name or exact name with a # + find examples/v1 -name $event_type -o -name "$event_type#*" | while read exline + do + echo " against $exline" + # run z-schema and dump stdout/err to the terminal (good for Jenkin's Console Output) and grep for fail messages + if [[ -n $(z-schema schema/v1/$event_type $exline 2>&1 | tee /dev/tty | grep -Ei "error|failed") ]]; then + echo " Failed." + exit 1 + fi + done +done