easol-canvas 0.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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