easol-canvas 0.1.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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