easol-canvas 0.1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas/check.rb +4 -1
  3. data/lib/canvas/checks/required_files_check.rb +3 -0
  4. data/lib/canvas/checks/valid_block_schemas_check.rb +72 -0
  5. data/lib/canvas/checks/valid_custom_types_check.rb +36 -0
  6. data/lib/canvas/checks/valid_footer_schema_check.rb +81 -0
  7. data/lib/canvas/checks/valid_html_check.rb +3 -0
  8. data/lib/canvas/checks/valid_json_check.rb +27 -0
  9. data/lib/canvas/checks/valid_liquid_check.rb +4 -1
  10. data/lib/canvas/checks/valid_menu_schema_check.rb +81 -0
  11. data/lib/canvas/checks.rb +4 -1
  12. data/lib/canvas/cli.rb +10 -1
  13. data/lib/canvas/constants.rb +31 -0
  14. data/lib/canvas/lint.rb +8 -4
  15. data/lib/canvas/offense.rb +3 -0
  16. data/lib/canvas/services/expand_attributes.rb +40 -0
  17. data/lib/canvas/services/fetch_custom_types.rb +27 -0
  18. data/lib/canvas/services/front_matter_extractor.rb +1 -0
  19. data/lib/canvas/validators/block_schema.rb +86 -0
  20. data/lib/canvas/validators/custom_type.rb +129 -0
  21. data/lib/canvas/validators/footer_schema.rb +29 -0
  22. data/lib/canvas/validators/html.rb +5 -2
  23. data/lib/canvas/validators/json.rb +24 -0
  24. data/lib/canvas/validators/liquid.rb +4 -1
  25. data/lib/canvas/validators/menu_schema.rb +95 -0
  26. data/lib/canvas/validators/schema_attribute.rb +146 -0
  27. data/lib/canvas/validators/schema_attributes/base.rb +104 -0
  28. data/lib/canvas/validators/schema_attributes/color.rb +81 -0
  29. data/lib/canvas/validators/schema_attributes/image.rb +45 -0
  30. data/lib/canvas/validators/schema_attributes/link.rb +51 -0
  31. data/lib/canvas/validators/schema_attributes/number.rb +17 -0
  32. data/lib/canvas/validators/schema_attributes/page.rb +49 -0
  33. data/lib/canvas/validators/schema_attributes/post.rb +49 -0
  34. data/lib/canvas/validators/schema_attributes/product.rb +49 -0
  35. data/lib/canvas/validators/schema_attributes/radio.rb +55 -0
  36. data/lib/canvas/validators/schema_attributes/range.rb +24 -0
  37. data/lib/canvas/validators/schema_attributes/select.rb +59 -0
  38. data/lib/canvas/validators/schema_attributes/variant.rb +49 -0
  39. data/lib/canvas/version.rb +3 -1
  40. data/lib/canvas.rb +21 -3
  41. metadata +47 -20
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3adb680aaee894634145000781ac9b4fbc7d4429f165bd70482c04f3fbdb7c6b
4
- data.tar.gz: b787e246b9be34acebcb80454504632a4b56e48dbd3e7c79755e33bf2272398a
3
+ metadata.gz: 5fe5e6dba572cf93a26d99560c7f36cd3f7f2188c019f5c5bd2429c6b897dd20
4
+ data.tar.gz: df34c9f374a73b17f47e4486345d6f6c21f4f184640d72976f664e3bf873e4ee
5
5
  SHA512:
6
- metadata.gz: 7d6db23a8b543fbace909d12fae0c4293ea040dccc11180d12f0265afb0b89f405b8d35fb5d957ac8f4a1c4de0e084c6bc82ebfe79bd809669634678863a9220
7
- data.tar.gz: 92323781eab651622fc54fe94f43305075ebcdfb9046c056152d62cc4645d77b03f65a2bf569739ad8d74e22366fc814a19d8bbce41a16a8aed9ab02e00d6ddb
6
+ metadata.gz: d42d0a2cecd8040521956b2bb2a7380c08471e1aebfaf162a652b61a532bda67de5426ffba517b75bbc50449ac65eccbc6df78e009103c6ccc5e86874184c732
7
+ data.tar.gz: b4590073ce09eef3e258f924b66bfb0f996b88dacfbfbc72c1d0120f8fe96fafc30911015ae31df76836d665efea529db0af9bf3184cd6212747993f8c3a5d1e
data/lib/canvas/check.rb CHANGED
@@ -1,6 +1,9 @@
1
- require 'cli/ui'
1
+ # frozen_string_literal: true
2
+
3
+ require "cli/ui"
2
4
 
3
5
  module Canvas
6
+ # :documented:
4
7
  class Check
5
8
  attr_reader :offenses
6
9
 
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Canvas
4
+ # :documented:
2
5
  class RequiredFilesCheck < Check
3
6
  REQUIRED_FILES = [
4
7
  "templates/product/index.{html,liquid}",
@@ -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,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ # This check will validate the JSON objects that represent the
6
+ # custom types that are defined in the /types directory.
7
+ class ValidCustomTypesCheck < Check
8
+ def run
9
+ custom_type_files.each do |filename|
10
+ schema = extract_json(filename)
11
+ validator = Validator::CustomType.new(schema: schema)
12
+
13
+ next if validator.validate
14
+
15
+ validator.errors.each do |message|
16
+ @offenses << Offense.new(
17
+ message: "Invalid Custom Type: #{filename} - \n#{message}"
18
+ )
19
+ end
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def custom_type_files
26
+ Dir.glob("types/*.json")
27
+ end
28
+
29
+ def extract_json(filename)
30
+ file = File.read(filename)
31
+ JSON.parse(file)
32
+ rescue JSON::ParserError
33
+ nil
34
+ end
35
+ end
36
+ 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
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Canvas
4
+ # :documented:
2
5
  class ValidHtmlCheck < Check
3
6
  def run
4
7
  html_files.each do |filename|
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ class ValidJsonCheck < Check
6
+ def run
7
+ json_files.each do |filename|
8
+ file = File.read(filename)
9
+ validator = Validator::Json.new(file)
10
+
11
+ next if validator.validate
12
+
13
+ validator.errors.each do |message|
14
+ @offenses << Offense.new(
15
+ message: "Invalid JSON: #{filename} - \n#{message}"
16
+ )
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def json_files
24
+ Dir.glob("**/*.json")
25
+ end
26
+ end
27
+ end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Canvas
4
+ # :documented:
2
5
  class ValidLiquidCheck < Check
3
6
  def run
4
7
  register_tags!
@@ -9,7 +12,7 @@ module Canvas
9
12
 
10
13
  next if validator.validate
11
14
 
12
- validator.errors.map(&:message).each do |message|
15
+ validator.errors.each do |message|
13
16
  @offenses << Offense.new(
14
17
  message: "Invalid Liquid: #{filename} - \n#{message}"
15
18
  )
@@ -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
data/lib/canvas/checks.rb CHANGED
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Canvas
4
+ # :documented:
2
5
  class Checks
3
6
  class << self
4
7
  def registered
@@ -10,7 +13,7 @@ module Canvas
10
13
  return if @checks.include?(klass)
11
14
  @checks << klass
12
15
  end
13
-
16
+
14
17
  def deregister_all!
15
18
  @checks = []
16
19
  end
data/lib/canvas/cli.rb CHANGED
@@ -1,12 +1,21 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "thor"
2
- require 'cli/ui'
4
+ require "cli/ui"
3
5
 
4
6
  module Canvas
7
+ # :documented:
5
8
  class Cli < Thor
6
9
  desc "lint", "Prints a hello world message"
7
10
  def lint
8
11
  CLI::UI::StdoutRouter.enable
9
12
  Canvas::Lint.new.run
10
13
  end
14
+
15
+ map %w[--version -v] => :__print_version
16
+ desc "--version, -v", "print the version"
17
+ def __print_version
18
+ puts Canvas::VERSION
19
+ end
11
20
  end
12
21
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # :documented:
4
+ # This file is used to define globally accessible constants.
5
+ module Canvas
6
+ module Constants
7
+ COLOR_PALETTE_VALUES = %w[
8
+ primary
9
+ secondary
10
+ body-bg
11
+ body-color
12
+ ].freeze
13
+
14
+ PRIMITIVE_TYPES = %w[
15
+ image
16
+ product
17
+ post
18
+ page
19
+ link
20
+ text
21
+ string
22
+ boolean
23
+ number
24
+ color
25
+ select
26
+ range
27
+ radio
28
+ variant
29
+ ].freeze
30
+ end
31
+ end
data/lib/canvas/lint.rb CHANGED
@@ -1,6 +1,9 @@
1
- require 'cli/ui'
1
+ # frozen_string_literal: true
2
+
3
+ require "cli/ui"
2
4
 
3
5
  module Canvas
6
+ #:documented:
4
7
  class Lint
5
8
  def run
6
9
  output_context = CLI::UI::SpinGroup.new(auto_debrief: false)
@@ -15,6 +18,7 @@ module Canvas
15
18
 
16
19
  if @checks.any?(&:failed?)
17
20
  puts debrief_message
21
+ exit 1
18
22
  end
19
23
  end
20
24
 
@@ -28,13 +32,13 @@ module Canvas
28
32
  end
29
33
 
30
34
  def debrief_message
31
- CLI::UI::Frame.open('Failures', color: :red) do
35
+ CLI::UI::Frame.open("Failures", color: :red) do
32
36
  failed_checks = @checks.filter(&:failed?)
33
37
  failed_checks.map do |check|
34
38
  CLI::UI::Frame.open(check.class.name, color: :red) do
35
- output = check.offenses.map do |offense|
39
+ output = check.offenses.map { |offense|
36
40
  CLI::UI.fmt "{{x}} #{offense.message}"
37
- end
41
+ }
38
42
  puts output.join("\n")
39
43
  end
40
44
  end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Canvas
4
+ # :documented:
2
5
  class Offense
3
6
  attr_reader :message
4
7
 
@@ -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
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module Canvas
3
4
  # :documented:
4
5
  # This service can be used to extract front matter from a liquid string.
@@ -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