easol-canvas 0.1.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas/checks/valid_block_schemas_check.rb +72 -0
  3. data/lib/canvas/checks/valid_footer_schema_check.rb +81 -0
  4. data/lib/canvas/checks/valid_json_check.rb +24 -0
  5. data/lib/canvas/checks/valid_liquid_check.rb +1 -1
  6. data/lib/canvas/checks/valid_menu_schema_check.rb +81 -0
  7. data/lib/canvas/constants.rb +12 -0
  8. data/lib/canvas/services/expand_attributes.rb +40 -0
  9. data/lib/canvas/services/fetch_custom_types.rb +27 -0
  10. data/lib/canvas/validators/block_schema.rb +86 -0
  11. data/lib/canvas/validators/footer_schema.rb +29 -0
  12. data/lib/canvas/validators/html.rb +2 -2
  13. data/lib/canvas/validators/json.rb +21 -0
  14. data/lib/canvas/validators/liquid.rb +1 -1
  15. data/lib/canvas/validators/menu_schema.rb +95 -0
  16. data/lib/canvas/validators/schema_attribute.rb +147 -0
  17. data/lib/canvas/validators/schema_attributes/base.rb +104 -0
  18. data/lib/canvas/validators/schema_attributes/color.rb +81 -0
  19. data/lib/canvas/validators/schema_attributes/image.rb +45 -0
  20. data/lib/canvas/validators/schema_attributes/link.rb +51 -0
  21. data/lib/canvas/validators/schema_attributes/number.rb +17 -0
  22. data/lib/canvas/validators/schema_attributes/page.rb +49 -0
  23. data/lib/canvas/validators/schema_attributes/post.rb +49 -0
  24. data/lib/canvas/validators/schema_attributes/product.rb +49 -0
  25. data/lib/canvas/validators/schema_attributes/radio.rb +55 -0
  26. data/lib/canvas/validators/schema_attributes/range.rb +24 -0
  27. data/lib/canvas/validators/schema_attributes/select.rb +59 -0
  28. data/lib/canvas/validators/schema_attributes/variant.rb +49 -0
  29. data/lib/canvas/version.rb +1 -1
  30. data/lib/canvas.rb +14 -3
  31. metadata +43 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 466969ef38180518fc021186d0c5fa694a3ddf947bae466c7cabed401fa44c1e
4
- data.tar.gz: 2521767c9f3eff3944c4b897b503c14715d52abf6642e30f1d330403ea167d85
3
+ metadata.gz: 8570c1c4d1a8f7097f621722730ca8c882b633b901c8d22fb13f7dcbcb2ac8df
4
+ data.tar.gz: d10b1319c6e1be3e0e43a77575fbf73f71f4a9f77582486d5b34d374d9c42105
5
5
  SHA512:
6
- metadata.gz: 6b49d991c487acd1fe4bac1219a3c67d98b68ba56757ccd4fc7b7310f0532942bc1377431b1ec29e729ffff77d00cb6a63313e6cd07329080970720036b2f29f
7
- data.tar.gz: 616a6b356396c5aee908a21449633508d1336ad695a6f5b0e690b276f1d1b8eade52cd0eb919329199d7cd661066f60fce6d2c3f8a1b1639094c5b3f00debad0
6
+ metadata.gz: 2dbfd55872ab41728e9243392a02c64551379cf42b99f59e455ade832fc028aa154b78e93600ec8bba8ac4fdcc52001850f2fd9cead452d64b896e1a997a0c63
7
+ data.tar.gz: d6507b81b2fe0f72aad4407601606fa305b45e0d2142753eccc03cda5760277ceeaf54f83b6d8468274490e33afa4360da7174dd23599b4e5aa0a9fe484ab5d3
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ # This check will validate the schema defined in the front matter
6
+ # within each block template file.
7
+ #
8
+ # Example of block Liquid with valid front matter:
9
+ #
10
+ # ---
11
+ # my_title:
12
+ # type: string
13
+ # my_color:
14
+ # type: color
15
+ # label: My color
16
+ # hint: "Select your favourite color"
17
+ # ---
18
+ #
19
+ # <p>My block HTML</p>
20
+ #
21
+ class ValidBlockSchemasCheck < Check
22
+ def run
23
+ custom_types = Canvas::FetchCustomTypes.call
24
+ block_files.each do |filename|
25
+ file = File.read(filename)
26
+ front_matter = extract_front_matter(file)
27
+ validate_format(filename, front_matter) &&
28
+ validate_schema(filename, front_matter, custom_types)
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def block_files
35
+ Dir.glob("blocks/**/*.{html,liquid}")
36
+ end
37
+
38
+ def validate_format(filename, front_matter)
39
+ return true if front_matter.is_a?(Hash) &&
40
+ front_matter.values.all? { |attr| attr.is_a?(Hash) }
41
+
42
+ @offenses << Offense.new(
43
+ message: "Invalid Block Schema: #{filename} - \nSchema is not in a valid format"
44
+ )
45
+ false
46
+ end
47
+
48
+ def validate_schema(filename, front_matter, custom_types)
49
+ schema = extract_schema(front_matter)
50
+ validator = Validator::BlockSchema.new(schema: schema, custom_types: custom_types)
51
+ return if validator.validate
52
+
53
+ validator.errors.each do |message|
54
+ @offenses << Offense.new(
55
+ message: "Invalid Block Schema: #{filename} - \n#{message}"
56
+ )
57
+ end
58
+ end
59
+
60
+ def extract_front_matter(file)
61
+ extractor = Canvas::FrontMatterExtractor.new(file)
62
+ front_matter = extractor.front_matter
63
+ front_matter.nil? ? {} : YAML.safe_load(front_matter)
64
+ end
65
+
66
+ def extract_schema(front_matter)
67
+ {
68
+ "attributes" => Canvas::ExpandAttributes.call(front_matter)
69
+ }
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ # This check will validate the schema defined in the front matter
6
+ # within each footer template file.
7
+ #
8
+ # Example of footer Liquid with valid front matter:
9
+ #
10
+ # ---
11
+ # max_item_levels: 2
12
+ # supports_open_new_tab: true
13
+ # attributes:
14
+ # my_title:
15
+ # type: string
16
+ # my_color:
17
+ # type: color
18
+ # label: My color
19
+ # hint: "Select your favourite color"
20
+ # ---
21
+ #
22
+ # <p>My footer HTML</p>
23
+ #
24
+ class ValidFooterSchemaCheck < Check
25
+ def run
26
+ file = File.read(footer_filename)
27
+ front_matter = extract_front_matter(file)
28
+ validate_format(front_matter) &&
29
+ validate_schema(front_matter)
30
+ end
31
+
32
+ private
33
+
34
+ def footer_filename
35
+ Dir.glob("partials/footer/index.{html,liquid}").first
36
+ end
37
+
38
+ def validate_format(front_matter)
39
+ return true if front_matter.is_a?(Hash) && attributes_valid_format(front_matter)
40
+
41
+ @offenses << Offense.new(
42
+ message: "Invalid Footer Schema: #{footer_filename} - \nSchema is not in a valid format"
43
+ )
44
+ false
45
+ end
46
+
47
+ def attributes_valid_format(front_matter)
48
+ return true unless front_matter.key?("attributes")
49
+
50
+ front_matter["attributes"].is_a?(Hash) &&
51
+ front_matter["attributes"].values.all? { |attr| attr.is_a?(Hash) }
52
+ end
53
+
54
+ def validate_schema(front_matter)
55
+ schema = extract_schema(front_matter)
56
+ validator = Validator::FooterSchema.new(
57
+ schema: schema,
58
+ custom_types: Canvas::FetchCustomTypes.call
59
+ )
60
+ return if validator.validate
61
+
62
+ validator.errors.each do |message|
63
+ @offenses << Offense.new(
64
+ message: "Invalid Footer Schema: #{footer_filename} - \n#{message}"
65
+ )
66
+ end
67
+ end
68
+
69
+ def extract_front_matter(file)
70
+ extractor = Canvas::FrontMatterExtractor.new(file)
71
+ front_matter = extractor.front_matter
72
+ front_matter.nil? ? {} : YAML.safe_load(front_matter)
73
+ end
74
+
75
+ def extract_schema(front_matter)
76
+ front_matter.merge(
77
+ "attributes" => Canvas::ExpandAttributes.call(front_matter["attributes"])
78
+ )
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,24 @@
1
+ module Canvas
2
+ class ValidJsonCheck < Check
3
+ def run
4
+ json_files.each do |filename|
5
+ file = File.read(filename)
6
+ validator = Validator::Json.new(file)
7
+
8
+ next if validator.validate
9
+
10
+ validator.errors.each do |message|
11
+ @offenses << Offense.new(
12
+ message: "Invalid JSON: #{filename} - \n#{message}"
13
+ )
14
+ end
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def json_files
21
+ Dir.glob("**/*.json")
22
+ end
23
+ end
24
+ end
@@ -9,7 +9,7 @@ module Canvas
9
9
 
10
10
  next if validator.validate
11
11
 
12
- validator.errors.map(&:message).each do |message|
12
+ validator.errors.each do |message|
13
13
  @offenses << Offense.new(
14
14
  message: "Invalid Liquid: #{filename} - \n#{message}"
15
15
  )
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ # This check will validate the schema defined in the front matter
6
+ # within each menu template file.
7
+ #
8
+ # Example of menu Liquid with valid front matter:
9
+ #
10
+ # ---
11
+ # max_item_levels: 2
12
+ # supports_open_new_tab: true
13
+ # attributes:
14
+ # my_title:
15
+ # type: string
16
+ # my_color:
17
+ # type: color
18
+ # label: My color
19
+ # hint: "Select your favourite color"
20
+ # ---
21
+ #
22
+ # <p>My menu HTML</p>
23
+ #
24
+ class ValidMenuSchemaCheck < Check
25
+ def run
26
+ file = File.read(menu_filename)
27
+ front_matter = extract_front_matter(file)
28
+ validate_format(front_matter) &&
29
+ validate_schema(front_matter)
30
+ end
31
+
32
+ private
33
+
34
+ def menu_filename
35
+ Dir.glob("partials/menu/index.{html,liquid}").first
36
+ end
37
+
38
+ def validate_format(front_matter)
39
+ return true if front_matter.is_a?(Hash) && attributes_valid_format(front_matter)
40
+
41
+ @offenses << Offense.new(
42
+ message: "Invalid Menu Schema: #{menu_filename} - \nSchema is not in a valid format"
43
+ )
44
+ false
45
+ end
46
+
47
+ def attributes_valid_format(front_matter)
48
+ return true unless front_matter.key?("attributes")
49
+
50
+ front_matter["attributes"].is_a?(Hash) &&
51
+ front_matter["attributes"].values.all? { |attr| attr.is_a?(Hash) }
52
+ end
53
+
54
+ def validate_schema(front_matter)
55
+ schema = extract_schema(front_matter)
56
+ validator = Validator::MenuSchema.new(
57
+ schema: schema,
58
+ custom_types: Canvas::FetchCustomTypes.call
59
+ )
60
+ return if validator.validate
61
+
62
+ validator.errors.each do |message|
63
+ @offenses << Offense.new(
64
+ message: "Invalid Menu Schema: #{menu_filename} - \n#{message}"
65
+ )
66
+ end
67
+ end
68
+
69
+ def extract_front_matter(file)
70
+ extractor = Canvas::FrontMatterExtractor.new(file)
71
+ front_matter = extractor.front_matter
72
+ front_matter.nil? ? {} : YAML.safe_load(front_matter)
73
+ end
74
+
75
+ def extract_schema(front_matter)
76
+ front_matter.merge(
77
+ "attributes" => Canvas::ExpandAttributes.call(front_matter["attributes"])
78
+ )
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ module Constants
5
+ COLOR_PALETTE_VALUES = %w[
6
+ primary
7
+ secondary
8
+ body-bg
9
+ body-color
10
+ ].freeze
11
+ end
12
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ # This service will convert the attributes from a hash, as they are defined in the front matter,
6
+ # into an array of hashes that the {Canvas::Validator::SchemaAttribute} class expects.
7
+ # e.g. the following front matter:
8
+ #
9
+ # my_title:
10
+ # type: string
11
+ # my_color:
12
+ # type: color
13
+ #
14
+ # will get converted to:
15
+ #
16
+ # [
17
+ # {
18
+ # "name" => "my_title",
19
+ # "type" => "string"
20
+ # },
21
+ # {
22
+ # "name" => "my_color",
23
+ # "type" => "color"
24
+ # }
25
+ # ]
26
+ #
27
+ class ExpandAttributes
28
+ class << self
29
+ # @param attributes_hash [Hash] hash of attributes pulled from front matter
30
+ # @return [Array<Hash>] array of hashes that represent each attribute
31
+ def call(attributes_hash)
32
+ return [] if attributes_hash.nil?
33
+
34
+ attributes_hash.each_with_object([]) do |(name, attribute_hash), attrs|
35
+ attrs << attribute_hash.merge("name" => name)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Canvas
6
+ # :documented:
7
+ # This service can be used to fetch the custom types from the /types directory.
8
+ class FetchCustomTypes
9
+ class << self
10
+ # @return [Array<Hash>] a list of all the custom types defined in the
11
+ # theme within the /types directory.
12
+ def call
13
+ filenames = Dir.glob("types/*.json")
14
+ filenames.map { |filename| extract_json(filename) }.compact
15
+ end
16
+
17
+ private
18
+
19
+ def extract_json(filename)
20
+ file = File.read(filename)
21
+ JSON.parse(file)
22
+ rescue JSON::ParserError
23
+ nil
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "nokogiri"
4
+ require "liquid"
5
+
6
+ module Canvas
7
+ module Validator
8
+ # :documented:
9
+ # This class is used to validate a schema for a block.
10
+ # Example of a valid block schema:
11
+ # {
12
+ # "attributes" => [
13
+ # {
14
+ # "name" => "my_title",
15
+ # "type" => "string"
16
+ # },
17
+ # {
18
+ # "name" => "my_color",
19
+ # "type" => "color",
20
+ # "label" => "My color",
21
+ # "hint" => "Select your favourite color"
22
+ # }
23
+ # ]
24
+ # }
25
+ class BlockSchema
26
+ PERMITTED_KEYS = %w[attributes].freeze
27
+
28
+ attr_reader :errors, :schema
29
+
30
+ # @param schema [Hash] the schema to be validated
31
+ # @param custom_types [Array<Hash>] a list of custom types
32
+ def initialize(schema:, custom_types: [])
33
+ @schema = schema
34
+ @custom_types = custom_types
35
+ @errors = []
36
+ end
37
+
38
+ def validate
39
+ if ensure_valid_format
40
+ ensure_no_unrecognized_keys
41
+ ensure_attributes_are_valid
42
+ end
43
+
44
+ @errors.empty?
45
+ end
46
+
47
+ private
48
+
49
+ def ensure_valid_format
50
+ return true if schema.is_a?(Hash) &&
51
+ (schema["attributes"].nil? || attributes_array_of_hashes?(schema))
52
+
53
+ @errors << "Schema is not in a valid format"
54
+ false
55
+ end
56
+
57
+ def ensure_no_unrecognized_keys
58
+ unrecognized_keys = schema.keys - PERMITTED_KEYS
59
+ return true if unrecognized_keys.empty?
60
+
61
+ @errors << "Unrecognized keys: #{unrecognized_keys.join(', ')}"
62
+ false
63
+ end
64
+
65
+ def ensure_attributes_are_valid
66
+ return true unless schema["attributes"]
67
+
68
+ schema["attributes"].each do |attribute_schema|
69
+ attr_validator = Validator::SchemaAttribute.new(
70
+ attribute: attribute_schema,
71
+ custom_types: @custom_types
72
+ )
73
+ next if attr_validator.validate
74
+
75
+ @errors << "Attribute \"#{attribute_schema['name']}\" is invalid "\
76
+ "- #{attr_validator.errors.join(', ')}"
77
+ end
78
+ end
79
+
80
+ def attributes_array_of_hashes?(schema)
81
+ schema["attributes"].is_a?(Array) &&
82
+ schema["attributes"].all? { |attr| attr.is_a?(Hash) }
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # We need to require this file t ensure it is loaded first
4
+ require_relative "menu_schema"
5
+
6
+ module Canvas
7
+ module Validator
8
+ # :documented:
9
+ # This class is used to validate a schema for a footer. For now the logic is exactly the
10
+ # same as the menu schema.
11
+ #
12
+ # Example:
13
+ # {
14
+ # "max_item_levels": 2,
15
+ # "supports_open_new_tab": "true",
16
+ # "attributes": {
17
+ # "fixed": {
18
+ # "group": "design",
19
+ # "label": "Fixed when scrolling",
20
+ # "hint": "The menu will stay fixed to the top when scrolling down the page.",
21
+ # "type": "boolean",
22
+ # "default: "false"
23
+ # }
24
+ # }
25
+ # }
26
+ #
27
+ class FooterSchema < MenuSchema; end
28
+ end
29
+ end
@@ -4,8 +4,8 @@ require "liquid"
4
4
  module Canvas
5
5
  module Validator
6
6
  class Html
7
- LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
8
- LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
7
+ LIQUID_TAG = /#{::Liquid::TagStart}.*?#{::Liquid::TagEnd}/om
8
+ LIQUID_VARIABLE = /#{::Liquid::VariableStart}.*?#{::Liquid::VariableEnd}/om
9
9
  LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
10
10
 
11
11
  attr_reader :errors
@@ -0,0 +1,21 @@
1
+ require "json"
2
+
3
+ module Canvas
4
+ module Validator
5
+ class Json
6
+ attr_reader :errors
7
+
8
+ def initialize(file)
9
+ @file = file
10
+ end
11
+
12
+ def validate
13
+ ::JSON.parse(@file)
14
+ true
15
+ rescue ::JSON::ParserError => e
16
+ @errors = [e.message]
17
+ false
18
+ end
19
+ end
20
+ end
21
+ end
@@ -19,7 +19,7 @@ module Canvas
19
19
  )
20
20
  true
21
21
  rescue ::Liquid::SyntaxError => e
22
- @errors = [e]
22
+ @errors = [e.message]
23
23
  false
24
24
  end
25
25
 
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ module Validator
5
+ # :documented:
6
+ # This class is used to validate a schema for a menu.
7
+ #
8
+ # Example:
9
+ # {
10
+ # "max_item_levels": 2,
11
+ # "supports_open_new_tab": "true",
12
+ # "attributes": {
13
+ # "fixed": {
14
+ # "group": "design",
15
+ # "label": "Fixed when scrolling",
16
+ # "hint": "The menu will stay fixed to the top when scrolling down the page.",
17
+ # "type": "boolean",
18
+ # "default: "false"
19
+ # }
20
+ # }
21
+ # }
22
+ #
23
+ class MenuSchema
24
+ PERMITTED_KEYS = %w[max_item_levels supports_open_new_tab attributes].freeze
25
+ ADDITIONAL_RESERVED_NAMES = %w[items type].freeze
26
+
27
+ attr_reader :schema, :errors
28
+
29
+ # @param schema [Hash] the schema to be validated
30
+ # @param custom_types [Array<Hash>] a list of custom types
31
+ def initialize(schema:, custom_types: [])
32
+ @schema = schema
33
+ @custom_types = custom_types
34
+ @errors = []
35
+ end
36
+
37
+ def validate
38
+ if ensure_valid_format
39
+ ensure_no_unrecognized_keys
40
+ ensure_max_item_levels_is_valid
41
+ ensure_attributes_are_valid
42
+ end
43
+
44
+ @errors.empty?
45
+ end
46
+
47
+ private
48
+
49
+ def ensure_valid_format
50
+ return true if schema.is_a?(Hash) &&
51
+ (schema["attributes"].nil? || attributes_array_of_hashes?(schema))
52
+
53
+ @errors << "Schema is not in a valid format"
54
+ false
55
+ end
56
+
57
+ def ensure_no_unrecognized_keys
58
+ unrecognized_keys = schema.keys - PERMITTED_KEYS
59
+ return true if unrecognized_keys.empty?
60
+
61
+ @errors << "Unrecognized keys: #{unrecognized_keys.join(', ')}"
62
+ false
63
+ end
64
+
65
+ def ensure_max_item_levels_is_valid
66
+ return true unless schema.key?("max_item_levels")
67
+ return true if [1, 2, 3].include?(schema["max_item_levels"].to_i)
68
+
69
+ @errors << "\"max_item_levels\" must be a number between 1 and 3"
70
+ false
71
+ end
72
+
73
+ def ensure_attributes_are_valid
74
+ return true unless schema["attributes"]
75
+
76
+ schema["attributes"].each do |attribute_schema|
77
+ attr_validator = Validator::SchemaAttribute.new(
78
+ attribute: attribute_schema,
79
+ custom_types: @custom_types,
80
+ additional_reserved_names: ADDITIONAL_RESERVED_NAMES
81
+ )
82
+ next if attr_validator.validate
83
+
84
+ @errors << "Attribute \"#{attribute_schema['name']}\" is invalid "\
85
+ "- #{attr_validator.errors.join(', ')}"
86
+ end
87
+ end
88
+
89
+ def attributes_array_of_hashes?(schema)
90
+ schema["attributes"].is_a?(Array) &&
91
+ schema["attributes"].all? { |attr| attr.is_a?(Hash) }
92
+ end
93
+ end
94
+ end
95
+ end