easol-canvas 0.1.1 → 1.2.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 (44) 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/valid_sass_check.rb +30 -0
  12. data/lib/canvas/checks.rb +4 -1
  13. data/lib/canvas/cli.rb +10 -1
  14. data/lib/canvas/constants.rb +31 -0
  15. data/lib/canvas/lint.rb +7 -4
  16. data/lib/canvas/offense.rb +3 -0
  17. data/lib/canvas/services/expand_attributes.rb +40 -0
  18. data/lib/canvas/services/fetch_custom_types.rb +27 -0
  19. data/lib/canvas/services/front_matter_extractor.rb +1 -0
  20. data/lib/canvas/validators/block_schema.rb +86 -0
  21. data/lib/canvas/validators/custom_type.rb +141 -0
  22. data/lib/canvas/validators/footer_schema.rb +29 -0
  23. data/lib/canvas/validators/html.rb +5 -2
  24. data/lib/canvas/validators/json.rb +24 -0
  25. data/lib/canvas/validators/liquid.rb +4 -1
  26. data/lib/canvas/validators/menu_schema.rb +95 -0
  27. data/lib/canvas/validators/sass.rb +26 -0
  28. data/lib/canvas/validators/schema_attribute.rb +146 -0
  29. data/lib/canvas/validators/schema_attributes/base.rb +104 -0
  30. data/lib/canvas/validators/schema_attributes/color.rb +81 -0
  31. data/lib/canvas/validators/schema_attributes/image.rb +45 -0
  32. data/lib/canvas/validators/schema_attributes/link.rb +51 -0
  33. data/lib/canvas/validators/schema_attributes/number.rb +17 -0
  34. data/lib/canvas/validators/schema_attributes/page.rb +49 -0
  35. data/lib/canvas/validators/schema_attributes/post.rb +49 -0
  36. data/lib/canvas/validators/schema_attributes/product.rb +49 -0
  37. data/lib/canvas/validators/schema_attributes/radio.rb +55 -0
  38. data/lib/canvas/validators/schema_attributes/range.rb +24 -0
  39. data/lib/canvas/validators/schema_attributes/select.rb +59 -0
  40. data/lib/canvas/validators/schema_attributes/variant.rb +49 -0
  41. data/lib/canvas/version.rb +3 -1
  42. data/lib/canvas.rb +30 -12
  43. data/lib/easol/canvas.rb +4 -0
  44. metadata +62 -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: 97ee44a992eb400c6fe2eedd35e6bb395ebc05e2f59f039ea9335194ff64fe83
4
+ data.tar.gz: 2684ff4ca3910fab9f0cd3bc36cb0c847f31565b1d3f8576964102a28773f247
5
5
  SHA512:
6
- metadata.gz: 6b49d991c487acd1fe4bac1219a3c67d98b68ba56757ccd4fc7b7310f0532942bc1377431b1ec29e729ffff77d00cb6a63313e6cd07329080970720036b2f29f
7
- data.tar.gz: 616a6b356396c5aee908a21449633508d1336ad695a6f5b0e690b276f1d1b8eade52cd0eb919329199d7cd661066f60fce6d2c3f8a1b1639094c5b3f00debad0
6
+ metadata.gz: 28c155af546e6898977757280894cba8ab26942aa1883c5b3ff752f3d7256b68c0631b9d18b6e29df8629d79e94a488739d2c9d0d1d1563dc8f4672f91d164ff
7
+ data.tar.gz: 3f743a067e8621d1b0893fe41afa8746b60dcc87a4398214b7d8b45ae66648088c22db84e2ed23b26df72c586f19b0547df3a6c8c354f36a005556262fef5a98
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
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Canvas
4
+ # :documented:
5
+ #
6
+ # This check will find all files ending in .css, .scss or .sass
7
+ # and run them through the sass validator - {Canvas::Validator::Sass}.
8
+ class ValidSassCheck < Check
9
+ def run
10
+ sass_files.each do |filename|
11
+ file = File.read(filename)
12
+ validator = Validator::Sass.new(file)
13
+
14
+ next if validator.validate
15
+
16
+ validator.errors.each do |message|
17
+ @offenses << Offense.new(
18
+ message: "Invalid Sass: #{filename} - \n#{message}"
19
+ )
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def sass_files
27
+ Dir.glob("**/*.{css,scss,sass}")
28
+ end
29
+ end
30
+ 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)
@@ -29,13 +32,13 @@ module Canvas
29
32
  end
30
33
 
31
34
  def debrief_message
32
- CLI::UI::Frame.open('Failures', color: :red) do
35
+ CLI::UI::Frame.open("Failures", color: :red) do
33
36
  failed_checks = @checks.filter(&:failed?)
34
37
  failed_checks.map do |check|
35
38
  CLI::UI::Frame.open(check.class.name, color: :red) do
36
- output = check.offenses.map do |offense|
39
+ output = check.offenses.map { |offense|
37
40
  CLI::UI.fmt "{{x}} #{offense.message}"
38
- end
41
+ }
39
42
  puts output.join("\n")
40
43
  end
41
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