From 61f09f0ff9bf6630854a10004c445bf0fcb5ed6f Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Mon, 9 Feb 2015 20:54:55 +0100 Subject: [PATCH 1/9] Created schema file for device.json files --- device_schema.json | 139 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 device_schema.json diff --git a/device_schema.json b/device_schema.json new file mode 100644 index 0000000..c444d43 --- /dev/null +++ b/device_schema.json @@ -0,0 +1,139 @@ +{ + "type": "object", + "properties": { + "show-by-default": { + "type": "boolean" + }, + "modes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "title": { + "type": "string" + }, + "orientation": { + "enum": ["vertical", "horizontal"] + }, + "images": { + "type": "array", + "items": { + "$ref": "#/definitions/image" + } + }, + "page-rect": { + "type": "object", + "properties": { + "left": { + "type": "number", + "minimum": 0 + }, + "top": { + "type": "number", + "minimum": 0 + }, + "width": { + "type": "number", + "minimum": 0 + }, + "height": { + "type": "number", + "minimum": 0 + } + }, + "required": ["left", "top", "width", "height"] + } + }, + "required": ["title", "orientation", "images", "page-rect"] + } + }, + "title": { + "type": "string" + }, + "screen": { + "type": "object", + "properties": { + "horizontal": { + "$ref": "#/definitions/orientation" + }, + "vertical": { + "$ref": "#/definitions/orientation" + }, + "device-pixel-ratio": { + "type": "number", + "minimum": 0, + "maximum": 100 + } + }, + "required": ["device-pixel-ratio", "vertical", "horizontal"] + }, + "capabilities": { + "type": "array", + "items": { + "enum": ["mobile", "touch"] + } + }, + "user-agent": { + "type": "string" + }, + "type": { + "enum": ["phone", "tablet", "notebook", "desktop", "unknown"] + } + }, + "required": ["title", "type", "screen", "user-agent", "show-by-default", "capabilities"], + "definitions": { + "image": { + "type": "object", + "properties": { + "src": { + "type": "string" + }, + "scale": { + "type": "number", + "minimum": 0 + } + }, + "required": ["src", "scale"] + }, + "orientation": { + "type": "object", + "properties": { + "width": { + "type": "number", + "minimum": 0, + "maximum": 10000 + }, + "height": { + "type": "number", + "minimum": 0, + "maximum": 10000 + }, + "outline": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "$ref": "#/definitions/image" + } + }, + "insets": { + "type": "object", + "properties": { + "left": { + "type": "number", + "minimum": 0 + }, + "top": { + "type": "number", + "minimum": 0 + } + } + } + } + } + }, + "required": ["width", "height"] + } + } +} \ No newline at end of file From 1325a7253eb164b4bc18b50b62f4729f78d87f86 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Mon, 9 Feb 2015 20:57:20 +0100 Subject: [PATCH 2/9] Added validation code using schema file --- generate_devices_list.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/generate_devices_list.py b/generate_devices_list.py index ce14c9b..69b9a46 100755 --- a/generate_devices_list.py +++ b/generate_devices_list.py @@ -10,6 +10,8 @@ try: except ImportError: import simplejson as json +from jsonschema import validate + POSSIBLE_TYPES = ["phone", "tablet", "notebook", "desktop", "unknown"] def load_and_parse_json(file_name): @@ -84,6 +86,8 @@ def parse_modes(modes, file_name, prefix, rebase_path): def parse_device_json(file_name, rebase_path): json = load_and_parse_json(file_name) + device_file_schema = load_and_parse_json("device_schema.json") + validate(json, device_file_schema) if not ("title" in json) or not isinstance(json["title"], basestring): raise_type_error(file_name, "title", "string") From aeadfa7257463e33541b76caadef324f86abd7b2 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Mon, 9 Feb 2015 21:04:19 +0100 Subject: [PATCH 3/9] Removed python validation code --- generate_devices_list.py | 97 ++++++---------------------------------- 1 file changed, 14 insertions(+), 83 deletions(-) diff --git a/generate_devices_list.py b/generate_devices_list.py index 69b9a46..d86a109 100755 --- a/generate_devices_list.py +++ b/generate_devices_list.py @@ -1,6 +1,5 @@ #!/usr/bin/env python -import numbers import os import os.path import sys @@ -12,8 +11,6 @@ except ImportError: from jsonschema import validate -POSSIBLE_TYPES = ["phone", "tablet", "notebook", "desktop", "unknown"] - def load_and_parse_json(file_name): try: with open(file_name, "r") as file: @@ -25,106 +22,40 @@ def load_and_parse_json(file_name): def raise_type_error(file_name, key, expected_type): raise Exception('ERROR: "' + key + '" must be of type "' + expected_type + '" (' + file_name + ')') -def parse_and_rebase_images(images, file_name, prefix, rebase_path): +def parse_and_rebase_images(images, rebase_path): for (index, entry) in enumerate(images): - i = str(index) - if not isinstance(entry, dict): - raise_type_error(file_name, prefix + "[" + i + "]", "object") - if not ("src" in entry) or not isinstance(entry["src"], basestring): - raise_type_error(file_name, prefix + "[" + i + "]/src", "string") - if not ("scale" in entry) or not isinstance(entry["scale"], numbers.Number): - raise_type_error(file_name, prefix + "[" + i + "]/scale", "number") entry["src"] = os.path.join(rebase_path, entry["src"]) -def parse_orientation(d, file_name, prefix, rebase_path): - if not ("width" in d) or not isinstance(d["width"], numbers.Number) or d["width"] < 0 or d["width"] > 10000 or abs(d["width"]) != d["width"]: +def parse_orientation(d, file_name, prefix): + if abs(d["width"]) != d["width"]: raise_type_error(file_name, prefix + "/width", "number") - if not ("height" in d) or not isinstance(d["height"], numbers.Number) or d["height"] < 0 or d["height"] > 10000 or abs(d["height"]) != d["height"]: + + if abs(d["height"]) != d["height"]: raise_type_error(file_name, prefix + "/height", "number") if not ("outline" in d): return + outline = d["outline"] - if not isinstance(outline, dict): - raise_type_error(file_name, prefix + "/outline", "object") - if not ("images" in outline) or not isinstance(outline["images"], (list, tuple)): - raise_type_error(file_name, prefix + "/outline/images", "array") - parse_and_rebase_images(outline["images"], file_name, prefix + "/outline/images", rebase_path) - if not ("insets" in outline) or not isinstance(outline["insets"], dict): - raise_type_error(file_name, prefix + "/outline/insets", "object") - if not ("left" in outline["insets"]) or not isinstance(outline["insets"]["left"], numbers.Number) or outline["insets"]["left"] < 0: - raise_type_error(file_name, prefix + "/outline/insets/left", "number") - if not ("top" in outline["insets"]) or not isinstance(outline["insets"]["top"], numbers.Number) or outline["insets"]["top"] < 0: - raise_type_error(file_name, prefix + "/outline/insets/top", "number") -def parse_modes(modes, file_name, prefix, rebase_path): + parse_and_rebase_images(outline["images"], file_name) + +def parse_modes(modes, file_name): for (index, mode) in enumerate(modes): - i = str(index) - if not isinstance(mode, dict): - raise_type_error(file_name, prefix + "[" + i + "]", "object") - if not ("title" in mode) or not isinstance(mode["title"], basestring): - raise_type_error(file_name, prefix + "[" + i + "]/title", "string") - if not ("orientation" in mode) or not isinstance(mode["orientation"], basestring): - raise_type_error(file_name, prefix + "[" + i + "]/orientation", "string") - if mode["orientation"] != "vertical" and mode["orientation"] != "horizontal": - raise Exception('ERROR: "' + prefix + '[' + i + ']/orientation" must be one of the following: [horizontal, vertical] (' + file_name + ')') - - if not ("images" in mode) or not isinstance(mode["images"], (list, tuple)): - raise_type_error(file_name, prefix + "[" + i + "]/images", "array") - parse_and_rebase_images(mode["images"], file_name, prefix + "[" + i + "]/images", rebase_path) - - if not ("page-rect" in mode) or not isinstance(mode["page-rect"], dict): - raise_type_error(file_name, prefix + "[" + i + "]/page-rect", "object") - if not ("left" in mode["page-rect"]) or not isinstance(mode["page-rect"]["left"], numbers.Number) or mode["page-rect"]["left"] < 0: - raise_type_error(file_name, prefix + "[" + i + "]/page-rect/left", "object") - if not ("top" in mode["page-rect"]) or not isinstance(mode["page-rect"]["top"], numbers.Number) or mode["page-rect"]["top"] < 0: - raise_type_error(file_name, prefix + "[" + i + "]/page-rect/top", "object") - if not ("width" in mode["page-rect"]) or not isinstance(mode["page-rect"]["width"], numbers.Number) or mode["page-rect"]["width"] < 0: - raise_type_error(file_name, prefix + "[" + i + "]/page-rect/width", "object") - if not ("height" in mode["page-rect"]) or not isinstance(mode["page-rect"]["height"], numbers.Number) or mode["page-rect"]["height"] < 0: - raise_type_error(file_name, prefix + "[" + i + "]/page-rect/height", "object") + parse_and_rebase_images(mode["images"], file_name) def parse_device_json(file_name, rebase_path): json = load_and_parse_json(file_name) device_file_schema = load_and_parse_json("device_schema.json") validate(json, device_file_schema) - if not ("title" in json) or not isinstance(json["title"], basestring): - raise_type_error(file_name, "title", "string") + screen = json["screen"] - if not ("type" in json) or not isinstance(json["type"], basestring): - raise_type_error(file_name, "type", "string") - if not (json["type"] in POSSIBLE_TYPES): - raise Exception('ERROR: "type" must be one of the following: [' + ", ".join(POSSIBLE_TYPES) + '] (' + file_name + ')') - - if not ("user-agent" in json) or not isinstance(json["user-agent"], basestring): - raise_type_error(file_name, "user-agent", "string") - - if not ("show-by-default" in json) or not isinstance(json["show-by-default"], bool): - raise_type_error(file_name, "show-by-default", "boolean") - - if not ("capabilities" in json) or not isinstance(json["capabilities"], (list, tuple)): - raise_type_error(file_name, "capabilities", "array") - for cap in json["capabilities"]: - if not isinstance(cap, basestring): - raise_type_error(file_name, "capability", "string") - - if ("screen" in json) and isinstance(json["screen"], dict): - screen = json["screen"] - if not ("device-pixel-ratio" in screen) or not isinstance(screen["device-pixel-ratio"], numbers.Number) or screen["device-pixel-ratio"] < 0 or screen["device-pixel-ratio"] > 100: - raise_type_error(file_name, "screen/device-pixel-ratio", "number") - - for orientation in ["vertical", "horizontal"]: - if not (orientation in screen) or not isinstance(screen[orientation], dict): - raise_type_error(file_name, "screen/" + orientation, "object") - parse_orientation(screen[orientation], file_name, "screen/" + orientation, rebase_path) - else: - raise_type_error(file_name, "screen", "object") + for orientation in ["vertical", "horizontal"]: + parse_orientation(screen[orientation], file_name, "screen/" + orientation) if "modes" in json: - if not isinstance(json["modes"], (list, tuple)): - raise_type_error(file_name, "modes", "array") - parse_modes(json["modes"], file_name, "modes", rebase_path) + parse_modes(json["modes"], file_name) return json From bdc32216732e68c61f48d819036aae48f09f8c89 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Mon, 9 Feb 2015 21:27:13 +0100 Subject: [PATCH 4/9] Fixed travis build --- .travis.yml | 1 + requirements.txt | 1 + 2 files changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/.travis.yml b/.travis.yml index 8b198f7..973fb68 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,5 @@ language: python python: - "2.7" +install: "pip install -r requirements.txt" script: python generate_devices_list.py \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..ccdf878 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +jsonschema==2.4.0 \ No newline at end of file From 40ced444b3f61effd8ae15cc7f0ec3dde4726fb1 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Tue, 10 Feb 2015 17:58:40 +0100 Subject: [PATCH 5/9] Disallowed fractional numbers inside orientation > width and height properties and all members of page-rect --- device_schema.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/device_schema.json b/device_schema.json index c444d43..695f07a 100644 --- a/device_schema.json +++ b/device_schema.json @@ -25,26 +25,26 @@ "type": "object", "properties": { "left": { - "type": "number", + "type": "integer", "minimum": 0 }, "top": { - "type": "number", + "type": "integer", "minimum": 0 }, "width": { - "type": "number", + "type": "integer", "minimum": 0 }, "height": { - "type": "number", + "type": "integer", "minimum": 0 } }, "required": ["left", "top", "width", "height"] } }, - "required": ["title", "orientation", "images", "page-rect"] + "required": ["title", "orientation"] } }, "title": { @@ -80,7 +80,7 @@ "enum": ["phone", "tablet", "notebook", "desktop", "unknown"] } }, - "required": ["title", "type", "screen", "user-agent", "show-by-default", "capabilities"], + "required": ["title", "type", "screen", "user-agent", "show-by-default"], "definitions": { "image": { "type": "object", @@ -99,12 +99,12 @@ "type": "object", "properties": { "width": { - "type": "number", + "type": "integer", "minimum": 0, "maximum": 10000 }, "height": { - "type": "number", + "type": "integer", "minimum": 0, "maximum": 10000 }, From b4d944f3b482919726fed4196ba490caf483df7f Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Tue, 10 Feb 2015 18:24:59 +0100 Subject: [PATCH 6/9] Added images and insets as required if outline is present --- device_schema.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/device_schema.json b/device_schema.json index 695f07a..ca7b68b 100644 --- a/device_schema.json +++ b/device_schema.json @@ -130,7 +130,8 @@ } } } - } + }, + "required": ["images", "insets"] } }, "required": ["width", "height"] From def3713b391e0681a4b515e5e113e915a3497681 Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Wed, 11 Feb 2015 16:23:06 +0100 Subject: [PATCH 7/9] Cleanup code Python code --- generate_devices_list.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/generate_devices_list.py b/generate_devices_list.py index d86a109..fbcbac9 100755 --- a/generate_devices_list.py +++ b/generate_devices_list.py @@ -9,7 +9,7 @@ try: except ImportError: import simplejson as json -from jsonschema import validate +import jsonschema def load_and_parse_json(file_name): try: @@ -23,31 +23,23 @@ def raise_type_error(file_name, key, expected_type): raise Exception('ERROR: "' + key + '" must be of type "' + expected_type + '" (' + file_name + ')') def parse_and_rebase_images(images, rebase_path): - for (index, entry) in enumerate(images): + for entry in images: entry["src"] = os.path.join(rebase_path, entry["src"]) def parse_orientation(d, file_name, prefix): - if abs(d["width"]) != d["width"]: - raise_type_error(file_name, prefix + "/width", "number") - - if abs(d["height"]) != d["height"]: - raise_type_error(file_name, prefix + "/height", "number") - if not ("outline" in d): return - outline = d["outline"] - - parse_and_rebase_images(outline["images"], file_name) + parse_and_rebase_images(d["outline"]["images"], file_name) def parse_modes(modes, file_name): - for (index, mode) in enumerate(modes): + for mode in modes: parse_and_rebase_images(mode["images"], file_name) def parse_device_json(file_name, rebase_path): json = load_and_parse_json(file_name) device_file_schema = load_and_parse_json("device_schema.json") - validate(json, device_file_schema) + jsonschema.validate(json, device_file_schema) screen = json["screen"] From 60ba477ce15c495824a7d55e1f150e83755c01fd Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Wed, 11 Feb 2015 21:54:25 +0100 Subject: [PATCH 8/9] rebase_path is passed correctly now --- generate_devices_list.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/generate_devices_list.py b/generate_devices_list.py index fbcbac9..a01226d 100755 --- a/generate_devices_list.py +++ b/generate_devices_list.py @@ -26,15 +26,15 @@ def parse_and_rebase_images(images, rebase_path): for entry in images: entry["src"] = os.path.join(rebase_path, entry["src"]) -def parse_orientation(d, file_name, prefix): +def parse_orientation(d, rebase_path): if not ("outline" in d): return - parse_and_rebase_images(d["outline"]["images"], file_name) + parse_and_rebase_images(d["outline"]["images"], rebase_path) -def parse_modes(modes, file_name): +def parse_modes(modes, rebase_path): for mode in modes: - parse_and_rebase_images(mode["images"], file_name) + parse_and_rebase_images(mode["images"], rebase_path) def parse_device_json(file_name, rebase_path): json = load_and_parse_json(file_name) @@ -44,10 +44,10 @@ def parse_device_json(file_name, rebase_path): screen = json["screen"] for orientation in ["vertical", "horizontal"]: - parse_orientation(screen[orientation], file_name, "screen/" + orientation) + parse_orientation(screen[orientation], rebase_path) if "modes" in json: - parse_modes(json["modes"], file_name) + parse_modes(json["modes"], rebase_path) return json From 1e100d3cdef750b497f716406a25d660d7b91f5c Mon Sep 17 00:00:00 2001 From: Marco Acierno Date: Thu, 12 Feb 2015 16:48:12 +0100 Subject: [PATCH 9/9] Remove raise_type_error, rename parse_and_rebase_images to rebase_images. parse_and_rebase_images has been renamed to rebase_images because it doesn't parse anything it just fixes images path. raise_type_error was needed when Python code was responsabile for json file validation but now jsonschema does everything --- generate_devices_list.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/generate_devices_list.py b/generate_devices_list.py index a01226d..bfb126c 100755 --- a/generate_devices_list.py +++ b/generate_devices_list.py @@ -19,10 +19,7 @@ def load_and_parse_json(file_name): print 'ERROR: Failed to parse %s' % file_name raise -def raise_type_error(file_name, key, expected_type): - raise Exception('ERROR: "' + key + '" must be of type "' + expected_type + '" (' + file_name + ')') - -def parse_and_rebase_images(images, rebase_path): +def rebase_images(images, rebase_path): for entry in images: entry["src"] = os.path.join(rebase_path, entry["src"]) @@ -30,11 +27,11 @@ def parse_orientation(d, rebase_path): if not ("outline" in d): return - parse_and_rebase_images(d["outline"]["images"], rebase_path) + rebase_images(d["outline"]["images"], rebase_path) def parse_modes(modes, rebase_path): for mode in modes: - parse_and_rebase_images(mode["images"], rebase_path) + rebase_images(mode["images"], rebase_path) def parse_device_json(file_name, rebase_path): json = load_and_parse_json(file_name)