easol-canvas 1.4.1 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5cc9522b87ab434f7121dfd557aa987b820c9934486da6c96e8c7706ac4f92b4
4
- data.tar.gz: e922ef25a0d8a916a82a7b9c4d8b4faf342088f6da65077518d72b68bf64eeab
3
+ metadata.gz: b20a9e978f946b59d3d3c9e2ee0b6763948c59278cc839bff63931879e3f18bb
4
+ data.tar.gz: 4dc2f01cbc06e1d60c99fd72211442bddfae47011cae0f237bec5eea12f2dddc
5
5
  SHA512:
6
- metadata.gz: c33da855f7ad39a4dad8172fc0942c9c60d4af81acf6c677bddb9c55dcacd9759392fa9c24e65910d76228a760bad31c244b865e8f83e52623ffdf26282f99b6
7
- data.tar.gz: e4d2f3ebae85a1ed14716a1f5514bd3211ae130b973475bbcaf3b52c946cb25db90cdcfbd75143db9984600e3604071d153eb1766e79388e172a2307e09abcfb
6
+ metadata.gz: 4ce1b8bab498d701f7ba028879060c7113ae231e59c9eeb2f1efb5d8ec6930043aeb1e6b061a5b121fa7f89ae470520482f8d2521aac7de606c3f2ef45be093e
7
+ data.tar.gz: cb1d28f8904587f6061ff31914f167cc055bc6bd95eb96f8acd8ff40550e5e2673f4d2386a12cf554f4976d8a5f425453a8e3e9c9ab24260ba05cbdbf93e6c5e
@@ -22,8 +22,9 @@ module Canvas
22
22
  def run
23
23
  custom_types = Canvas::FetchCustomTypes.call
24
24
  block_files.each do |filename|
25
- file = File.read(filename)
26
- front_matter = extract_front_matter(file)
25
+ front_matter = extract_front_matter(filename)
26
+ next unless front_matter
27
+
27
28
  validate_format(filename, front_matter) &&
28
29
  validate_schema(filename, front_matter, custom_types)
29
30
  end
@@ -57,10 +58,18 @@ module Canvas
57
58
  end
58
59
  end
59
60
 
60
- def extract_front_matter(file)
61
+ def extract_front_matter(filename)
62
+ file = File.read(filename)
63
+
61
64
  extractor = Canvas::FrontMatterExtractor.new(file)
62
65
  front_matter = extractor.front_matter
63
66
  front_matter.nil? ? {} : YAML.safe_load(front_matter)
67
+ rescue Psych::SyntaxError
68
+ @offenses << Offense.new(
69
+ message: "Invalid Block Schema: #{filename} - \nFront matter's YAML is not in a valid format"
70
+ )
71
+
72
+ nil
64
73
  end
65
74
  end
66
75
  end
@@ -51,8 +51,7 @@ module Canvas
51
51
  front_matter["attributes"].values.all? { |attr| attr.is_a?(Hash) }
52
52
  end
53
53
 
54
- def validate_schema(front_matter)
55
- schema = extract_schema(front_matter)
54
+ def validate_schema(schema)
56
55
  validator = Validator::FooterSchema.new(
57
56
  schema: schema,
58
57
  custom_types: Canvas::FetchCustomTypes.call
@@ -71,11 +70,5 @@ module Canvas
71
70
  front_matter = extractor.front_matter
72
71
  front_matter.nil? ? {} : YAML.safe_load(front_matter)
73
72
  end
74
-
75
- def extract_schema(front_matter)
76
- front_matter.merge(
77
- "attributes" => Canvas::ExpandAttributes.call(front_matter["attributes"])
78
- )
79
- end
80
73
  end
81
74
  end
@@ -51,8 +51,7 @@ module Canvas
51
51
  front_matter["attributes"].values.all? { |attr| attr.is_a?(Hash) }
52
52
  end
53
53
 
54
- def validate_schema(front_matter)
55
- schema = extract_schema(front_matter)
54
+ def validate_schema(schema)
56
55
  validator = Validator::MenuSchema.new(
57
56
  schema: schema,
58
57
  custom_types: Canvas::FetchCustomTypes.call
@@ -71,11 +70,5 @@ module Canvas
71
70
  front_matter = extractor.front_matter
72
71
  front_matter.nil? ? {} : YAML.safe_load(front_matter)
73
72
  end
74
-
75
- def extract_schema(front_matter)
76
- front_matter.merge(
77
- "attributes" => Canvas::ExpandAttributes.call(front_matter["attributes"])
78
- )
79
- end
80
73
  end
81
74
  end
@@ -9,17 +9,25 @@ module Canvas
9
9
  # This class is used to validate a schema for a block.
10
10
  # Example of a valid block schema:
11
11
  # {
12
- # "attributes" => [
13
- # {
14
- # "name" => "my_title",
12
+ # "attributes" => {
13
+ # "my_title" => {
15
14
  # "type" => "string"
16
15
  # },
17
- # {
18
- # "name" => "my_color",
16
+ # "my_color" => {
19
17
  # "type" => "color",
20
18
  # "label" => "My color",
21
19
  # "hint" => "Select your favourite color"
22
20
  # }
21
+ # },
22
+ # "layout" => [
23
+ # {
24
+ # "type" => "tab",
25
+ # "label" => "Content",
26
+ # "elements" => [
27
+ # "my_title",
28
+ # "my_color"
29
+ # ]
30
+ # }
23
31
  # ]
24
32
  # }
25
33
  class BlockSchema
@@ -51,7 +59,7 @@ module Canvas
51
59
 
52
60
  def ensure_valid_format
53
61
  return true if schema.is_a?(Hash) &&
54
- (schema["attributes"].nil? || attributes_array_of_hashes?(schema))
62
+ (schema["attributes"].nil? || attributes_hash_of_hashes?(schema))
55
63
 
56
64
  @errors << "Schema is not in a valid format"
57
65
  false
@@ -78,7 +86,8 @@ module Canvas
78
86
  def ensure_attributes_are_valid
79
87
  return true unless schema["attributes"]
80
88
 
81
- schema["attributes"].each do |attribute_schema|
89
+ attributes = Canvas::ExpandAttributes.call(schema["attributes"])
90
+ attributes.each do |attribute_schema|
82
91
  attr_validator = Validator::SchemaAttribute.new(
83
92
  attribute: attribute_schema,
84
93
  custom_types: @custom_types
@@ -90,22 +99,17 @@ module Canvas
90
99
  end
91
100
  end
92
101
 
93
- def attributes_array_of_hashes?(schema)
94
- schema["attributes"].is_a?(Array) &&
95
- schema["attributes"].all? { |attr| attr.is_a?(Hash) }
102
+ def attributes_hash_of_hashes?(schema)
103
+ schema["attributes"].is_a?(Hash) &&
104
+ schema["attributes"].values.all? { |attr| attr.is_a?(Hash) }
96
105
  end
97
106
 
107
+ # To support older schemas that do not nest the attributes
108
+ # under the `attributes` key.
98
109
  def normalize_schema(schema)
99
- if schema.key?("attributes")
100
- {
101
- **schema,
102
- "attributes" => Canvas::ExpandAttributes.call(schema["attributes"])
103
- }
104
- else
105
- {
106
- "attributes" => Canvas::ExpandAttributes.call(schema)
107
- }
108
- end
110
+ return schema if schema.key?("attributes")
111
+
112
+ { "attributes" => schema }
109
113
  end
110
114
  end
111
115
  end
@@ -11,19 +11,27 @@ module Canvas
11
11
  #
12
12
  # Example:
13
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"
14
+ # "max_item_levels" => 2,
15
+ # "supports_open_new_tab" => "true",
16
+ # "attributes" => {
17
+ # "text_color" => {
18
+ # "type" => "color"
19
+ # },
20
+ # "background_color" => {
21
+ # "type" => "color"
23
22
  # }
24
- # }
23
+ # },
24
+ # "layout" => [
25
+ # {
26
+ # "type" => "tab",
27
+ # "label" => "Content",
28
+ # "elements" => [
29
+ # "text_color",
30
+ # "background_color"
31
+ # ]
32
+ # }
33
+ # ]
25
34
  # }
26
- #
27
35
  class FooterSchema < MenuSchema; end
28
36
  end
29
37
  end
@@ -9,23 +9,38 @@ module Canvas
9
9
  # This class is used to validate a layout definition, part of block schema.
10
10
  # Example of a valid layout definition:
11
11
  # {
12
+ # "attributes" => {
13
+ # "title" => {
14
+ # "type" => "string"
15
+ # }
16
+ # ...
17
+ # },
12
18
  # "layout" => [
13
- # {
14
- # "label" => "Design",
15
- # "type" => "tab",
16
- # "elements" => [
17
- # "heading",
18
- # {
19
- # "type" => "accordion",
20
- # "label" => "Logo",
19
+ # {
20
+ # "label" => "Design",
21
+ # "type" => "tab",
21
22
  # "elements" => [
22
- # "description",
23
- # { "type" => "attribute", "name" => "logo_alt" },
24
- # "title"
25
- # ]
26
- # }
27
- # ]
28
- # }]
23
+ # "heading",
24
+ # {
25
+ # "type" => "accordion",
26
+ # "label" => "Logo",
27
+ # "elements" => [
28
+ # "description",
29
+ # { "type" => "attribute", "name" => "logo_alt" },
30
+ # "title"
31
+ # ]
32
+ # },
33
+ # {
34
+ # "type" => "accordion_toggle",
35
+ # "toggle_attribute" => "cta_enabled",
36
+ # "elements" => [
37
+ # "cta_text",
38
+ # "cta_target"
39
+ # ]
40
+ # }
41
+ # ]
42
+ # }
43
+ # ]
29
44
  # }
30
45
  class LayoutSchema
31
46
  attr_reader :errors
@@ -41,6 +56,8 @@ module Canvas
41
56
  if ensure_valid_format
42
57
  ensure_no_unrecognized_keys
43
58
  ensure_no_duplicate_keys
59
+ ensure_accordion_toggles_are_valid
60
+ ensure_unique_tabs
44
61
  end
45
62
 
46
63
  @errors.empty?
@@ -48,8 +65,25 @@ module Canvas
48
65
 
49
66
  private
50
67
 
68
+ attr_reader :schema
69
+
70
+ def ensure_unique_tabs
71
+ tabs = fetch_elements_of_type("tab")
72
+ duplicates =
73
+ tabs
74
+ .map { |item| [item[0]["label"], item[1]] }
75
+ .group_by { |(key, _)| key }
76
+ .filter { |key, usage| usage.size > 1 }
77
+
78
+ unless duplicates.empty?
79
+ duplicates.each do |tab, usage|
80
+ @errors << "Duplicated tab label `#{tab}` found. Location: #{usage.map { |(_, location)| location }.join(", ")}"
81
+ end
82
+ end
83
+ end
84
+
51
85
  def ensure_no_duplicate_keys
52
- attributes = gather_attributes_from_layout_schema
86
+ attributes = fetch_all_attribute_names
53
87
  duplicates =
54
88
  attributes
55
89
  .group_by { |(key)| key }
@@ -63,37 +97,54 @@ module Canvas
63
97
  end
64
98
 
65
99
  def ensure_no_unrecognized_keys
66
- attributes = gather_attributes_from_layout_schema
67
- defined_attributes = @schema["attributes"]&.map { |definition| normalize_attribute(definition["name"]) } || []
100
+ attributes = fetch_all_attribute_names
101
+ defined_attributes = expanded_attributes.map { |definition| normalize_attribute(definition["name"]) } || []
68
102
 
69
103
  attributes.each do |attribute, location|
70
104
  @errors << "Unrecognized attribute `#{attribute}`. Location: #{location}" unless defined_attributes.include?(attribute)
71
105
  end
72
106
  end
73
107
 
74
- def gather_attributes_from_layout_schema
75
- attribute_keys = []
108
+ # @return [Array<Array(String, String)>] an array of all the attribute names that
109
+ # are mentioned in the layout schema, along with its path. The names are
110
+ # normalized, i.e. downcased.
111
+ def fetch_all_attribute_names
112
+ attributes = fetch_elements_of_type("attribute")
113
+ attributes.map do |(node, path)|
114
+ [
115
+ normalize_attribute(node.is_a?(Hash) ? node["name"] : node),
116
+ path
117
+ ]
118
+ end
119
+ end
120
+
121
+ # @param type [String] the element type to fetch
122
+ # @return [Array<Array(<Hash, String>, String)] an array of elements that match
123
+ # the given type. Each element is an array containing the node and its path.
124
+ def fetch_elements_of_type(type)
125
+ elements = []
126
+
127
+ fetch_element = ->(node, path) {
128
+ if type == "attribute" && node.is_a?(String)
129
+ elements << [node, path]
130
+ elsif node["type"] == type
131
+ elements << [node, path]
132
+ end
76
133
 
77
- fetch_attribute_type = ->(node, path) {
78
134
  if node.is_a?(Hash) && node.key?("elements")
79
135
  node["elements"].each_with_index do |element, i|
80
136
  current_path = "#{path}/elements/#{i}"
81
- fetch_attribute_type.call(element, current_path)
137
+ fetch_element.call(element, current_path)
82
138
  end
83
- else
84
- attribute_keys << [
85
- normalize_attribute(node.is_a?(Hash) ? node["name"] : node),
86
- path
87
- ]
88
139
  end
89
140
  }
90
141
 
91
142
  layout_schema.each_with_index do |tab, i|
92
143
  current_path = "layout/#{i}"
93
- fetch_attribute_type.call(tab, current_path)
144
+ fetch_element.call(tab, current_path)
94
145
  end
95
146
 
96
- attribute_keys
147
+ elements
97
148
  end
98
149
 
99
150
  def ensure_valid_format
@@ -105,19 +156,40 @@ module Canvas
105
156
  false
106
157
  end
107
158
 
159
+ def ensure_accordion_toggles_are_valid
160
+ accordion_toggles = fetch_elements_of_type("accordion_toggle")
161
+ accordion_toggles.each do |accordion_toggle, location|
162
+ toggle_attribute = expanded_attributes.detect { |attr|
163
+ attr["name"] == accordion_toggle["toggle_attribute"]
164
+ }
165
+
166
+ if toggle_attribute.nil?
167
+ @errors << "The toggle_attribute in accordion_toggle is unrecognized. Location: #{location}"
168
+ elsif toggle_attribute["type"]&.downcase != "boolean"
169
+ @errors << "The toggle_attribute in accordion_toggle must be a boolean. Location: #{location}"
170
+ end
171
+ end
172
+ end
173
+
108
174
  def layout_schema
109
- @schema["layout"] || []
175
+ schema["layout"] || []
110
176
  end
111
177
 
112
178
  def schema_definition
113
179
  File.read(
114
- File.join(File.dirname(__FILE__), "../../../", "schema_definitions", "block_layout.json")
180
+ File.join(File.dirname(__FILE__), "../../../", "schema_definitions", "layout.json")
115
181
  )
116
182
  end
117
183
 
118
184
  def normalize_attribute(name)
119
185
  name.strip.downcase
120
186
  end
187
+
188
+ def expanded_attributes
189
+ return [] if schema["attributes"].nil?
190
+
191
+ @_expanded_attributes ||= Canvas::ExpandAttributes.call(schema["attributes"])
192
+ end
121
193
  end
122
194
  end
123
195
  end
@@ -7,21 +7,33 @@ module Canvas
7
7
  #
8
8
  # Example:
9
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"
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
+ # "background_color" => {
21
+ # "type" => "color"
19
22
  # }
20
- # }
23
+ # },
24
+ # "layout" => [
25
+ # {
26
+ # "type" => "tab",
27
+ # "label" => "Content",
28
+ # "elements" => [
29
+ # "fixed",
30
+ # "background_color"
31
+ # ]
32
+ # }
33
+ # ]
21
34
  # }
22
- #
23
35
  class MenuSchema
24
- PERMITTED_KEYS = %w[max_item_levels supports_open_new_tab attributes].freeze
36
+ PERMITTED_KEYS = %w[max_item_levels supports_open_new_tab attributes layout].freeze
25
37
  ADDITIONAL_RESERVED_NAMES = %w[items type].freeze
26
38
 
27
39
  attr_reader :schema, :errors
@@ -38,6 +50,7 @@ module Canvas
38
50
  if ensure_valid_format
39
51
  ensure_no_unrecognized_keys
40
52
  ensure_max_item_levels_is_valid
53
+ ensure_layout_is_valid
41
54
  ensure_attributes_are_valid
42
55
  end
43
56
 
@@ -48,7 +61,7 @@ module Canvas
48
61
 
49
62
  def ensure_valid_format
50
63
  return true if schema.is_a?(Hash) &&
51
- (schema["attributes"].nil? || attributes_array_of_hashes?(schema))
64
+ (schema["attributes"].nil? || attributes_hash_of_hashes?(schema))
52
65
 
53
66
  @errors << "Schema is not in a valid format"
54
67
  false
@@ -70,10 +83,21 @@ module Canvas
70
83
  false
71
84
  end
72
85
 
86
+ def ensure_layout_is_valid
87
+ return true unless schema["layout"]
88
+
89
+ layout_validator = LayoutSchema.new(schema: @schema)
90
+ return true if layout_validator.validate
91
+
92
+ @errors += layout_validator.errors
93
+ false
94
+ end
95
+
73
96
  def ensure_attributes_are_valid
74
97
  return true unless schema["attributes"]
75
98
 
76
- schema["attributes"].each do |attribute_schema|
99
+ attributes = Canvas::ExpandAttributes.call(schema["attributes"])
100
+ attributes.each do |attribute_schema|
77
101
  attr_validator = Validator::SchemaAttribute.new(
78
102
  attribute: attribute_schema,
79
103
  custom_types: @custom_types,
@@ -86,9 +110,9 @@ module Canvas
86
110
  end
87
111
  end
88
112
 
89
- def attributes_array_of_hashes?(schema)
90
- schema["attributes"].is_a?(Array) &&
91
- schema["attributes"].all? { |attr| attr.is_a?(Hash) }
113
+ def attributes_hash_of_hashes?(schema)
114
+ schema["attributes"].is_a?(Hash) &&
115
+ schema["attributes"].values.all? { |attr| attr.is_a?(Hash) }
92
116
  end
93
117
  end
94
118
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Canvas
4
- VERSION = "1.4.1"
4
+ VERSION = "2.1.0"
5
5
  end
@@ -24,20 +24,21 @@
24
24
  "items": {
25
25
  "oneOf": [
26
26
  { "type": "string" },
27
- { "$ref": "#/$defs/component" },
27
+ { "$ref": "#/$defs/accordion" },
28
+ { "$ref": "#/$defs/accordion_toggle" },
28
29
  { "$ref": "#/$defs/attribute" }
29
30
  ]
30
31
  }
31
32
  }
32
33
  }
33
34
  },
34
- "component": {
35
+ "accordion": {
35
36
  "type": "object",
36
37
  "required": ["label", "type"],
37
38
  "properties": {
38
39
  "type": {
39
40
  "type": "string",
40
- "enum": ["accordion"]
41
+ "const": "accordion"
41
42
  },
42
43
  "label": {
43
44
  "type": "string"
@@ -50,6 +51,25 @@
50
51
  }
51
52
  }
52
53
  },
54
+ "accordion_toggle": {
55
+ "type": "object",
56
+ "required": ["type", "toggle_attribute", "elements"],
57
+ "properties": {
58
+ "type": {
59
+ "type": "string",
60
+ "const": "accordion_toggle"
61
+ },
62
+ "toggle_attribute": {
63
+ "type": "string"
64
+ },
65
+ "elements": {
66
+ "type": "array",
67
+ "items": {
68
+ "oneOf": [{ "type": "string" }, { "$ref": "#/$defs/attribute" }]
69
+ }
70
+ }
71
+ }
72
+ },
53
73
  "attribute": {
54
74
  "type": "object",
55
75
  "required": ["name", "type"],
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: easol-canvas
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.1
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kyle Byrne
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2022-07-15 00:00:00.000000000 Z
12
+ date: 2022-08-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: thor
@@ -150,7 +150,7 @@ files:
150
150
  - lib/canvas/validators/schema_attributes/variant.rb
151
151
  - lib/canvas/version.rb
152
152
  - lib/easol/canvas.rb
153
- - schema_definitions/block_layout.json
153
+ - schema_definitions/layout.json
154
154
  homepage: https://rubygems.org/gems/easol-canvas
155
155
  licenses:
156
156
  - MIT
@@ -170,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
170
  - !ruby/object:Gem::Version
171
171
  version: '0'
172
172
  requirements: []
173
- rubygems_version: 3.2.33
173
+ rubygems_version: 3.3.11
174
174
  signing_key:
175
175
  specification_version: 4
176
176
  summary: CLI to help with building themes for Easol