easol-canvas 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3adb680aaee894634145000781ac9b4fbc7d4429f165bd70482c04f3fbdb7c6b
4
+ data.tar.gz: b787e246b9be34acebcb80454504632a4b56e48dbd3e7c79755e33bf2272398a
5
+ SHA512:
6
+ metadata.gz: 7d6db23a8b543fbace909d12fae0c4293ea040dccc11180d12f0265afb0b89f405b8d35fb5d957ac8f4a1c4de0e084c6bc82ebfe79bd809669634678863a9220
7
+ data.tar.gz: 92323781eab651622fc54fe94f43305075ebcdfb9046c056152d62cc4645d77b03f65a2bf569739ad8d74e22366fc814a19d8bbce41a16a8aed9ab02e00d6ddb
data/bin/canvas ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/canvas'
4
+
5
+ Canvas::Cli.start(ARGV)
data/bin/rspec ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'rspec' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ require "pathname"
12
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
13
+ Pathname.new(__FILE__).realpath)
14
+
15
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
16
+
17
+ if File.file?(bundle_binstub)
18
+ if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
19
+ load(bundle_binstub)
20
+ else
21
+ abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
22
+ Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
23
+ end
24
+ end
25
+
26
+ require "rubygems"
27
+ require "bundler/setup"
28
+
29
+ load Gem.bin_path("rspec-core", "rspec")
@@ -0,0 +1,19 @@
1
+ require 'cli/ui'
2
+
3
+ module Canvas
4
+ class Check
5
+ attr_reader :offenses
6
+
7
+ def initialize
8
+ @offenses = []
9
+ end
10
+
11
+ def run
12
+ raise NotImplementedError
13
+ end
14
+
15
+ def failed?
16
+ @offenses.any?
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ module Canvas
2
+ class RequiredFilesCheck < Check
3
+ REQUIRED_FILES = [
4
+ "templates/product/index.{html,liquid}",
5
+ "templates/blog_overview/index.{html,liquid}",
6
+ "templates/blog_post/index.{html,liquid}",
7
+ "partials/footer/index.{html,liquid}",
8
+ "partials/menu/index.{html,liquid}",
9
+ "assets/index.css"
10
+ ].freeze
11
+
12
+ def run
13
+ REQUIRED_FILES.each do |filename|
14
+ next unless Dir.glob(filename).empty?
15
+
16
+ @offenses << Offense.new(
17
+ message: "Missing file: #{filename}"
18
+ )
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module Canvas
2
+ class ValidHtmlCheck < Check
3
+ def run
4
+ html_files.each do |filename|
5
+ file = File.read(filename)
6
+ validator = Validator::Html.new(file)
7
+
8
+ next if validator.validate
9
+
10
+ validator.errors.map(&:message).each do |message|
11
+ @offenses << Offense.new(
12
+ message: "Invalid HTML: #{filename} - \n#{message}"
13
+ )
14
+ end
15
+ end
16
+ end
17
+
18
+ def html_files
19
+ Dir.glob("**/*.{html,liquid}")
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,40 @@
1
+ module Canvas
2
+ class ValidLiquidCheck < Check
3
+ def run
4
+ register_tags!
5
+
6
+ liquid_files.each do |filename|
7
+ file = File.read(filename)
8
+ validator = Validator::Liquid.new(file)
9
+
10
+ next if validator.validate
11
+
12
+ validator.errors.map(&:message).each do |message|
13
+ @offenses << Offense.new(
14
+ message: "Invalid Liquid: #{filename} - \n#{message}"
15
+ )
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def liquid_files
23
+ Dir.glob("**/*.{html,liquid}")
24
+ end
25
+
26
+ def register_tag(name, klass)
27
+ ::Liquid::Template.register_tag(name, klass)
28
+ end
29
+
30
+ # These are the tags that we define in the Rails app. If we add a new custom tag
31
+ # in the Rails app, we'll need to register it here.
32
+ def register_tags!
33
+ register_tag("form", ::Liquid::Block)
34
+ register_tag("product_search", ::Liquid::Block)
35
+ register_tag("easol_badge", ::Liquid::Tag)
36
+ register_tag("accommodation_availability", ::Liquid::Block)
37
+ register_tag("cache", ::Liquid::Block)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,19 @@
1
+ module Canvas
2
+ class Checks
3
+ class << self
4
+ def registered
5
+ @checks ||= []
6
+ end
7
+
8
+ def register(klass)
9
+ @checks ||= []
10
+ return if @checks.include?(klass)
11
+ @checks << klass
12
+ end
13
+
14
+ def deregister_all!
15
+ @checks = []
16
+ end
17
+ end
18
+ end
19
+ end
data/lib/canvas/cli.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "thor"
2
+ require 'cli/ui'
3
+
4
+ module Canvas
5
+ class Cli < Thor
6
+ desc "lint", "Prints a hello world message"
7
+ def lint
8
+ CLI::UI::StdoutRouter.enable
9
+ Canvas::Lint.new.run
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,44 @@
1
+ require 'cli/ui'
2
+
3
+ module Canvas
4
+ class Lint
5
+ def run
6
+ output_context = CLI::UI::SpinGroup.new(auto_debrief: false)
7
+
8
+ @checks = Checks.registered.map(&:new)
9
+
10
+ @checks.each do |check|
11
+ run_check(check, output_context)
12
+ end
13
+
14
+ output_context.wait
15
+
16
+ if @checks.any?(&:failed?)
17
+ puts debrief_message
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def run_check(check, output_context)
24
+ output_context.add(check.class.name) do
25
+ check.run
26
+ raise if check.offenses.any?
27
+ end
28
+ end
29
+
30
+ def debrief_message
31
+ CLI::UI::Frame.open('Failures', color: :red) do
32
+ failed_checks = @checks.filter(&:failed?)
33
+ failed_checks.map do |check|
34
+ CLI::UI::Frame.open(check.class.name, color: :red) do
35
+ output = check.offenses.map do |offense|
36
+ CLI::UI.fmt "{{x}} #{offense.message}"
37
+ end
38
+ puts output.join("\n")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,9 @@
1
+ module Canvas
2
+ class Offense
3
+ attr_reader :message
4
+
5
+ def initialize(message:)
6
+ @message = message
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+ module Canvas
3
+ # :documented:
4
+ # This service can be used to extract front matter from a liquid string.
5
+ class FrontMatterExtractor
6
+ attr_accessor :front_matter, :html
7
+
8
+ def initialize(full_markup)
9
+ @front_matter = extract_front_matter(full_markup)
10
+ @html = extract_html(full_markup)
11
+ end
12
+
13
+ private
14
+
15
+ # @return [String] the front matter, with the rest of the HTML stripped out
16
+ def extract_front_matter(full_markup)
17
+ return if full_markup.nil?
18
+
19
+ front_matter, body = scan(full_markup)
20
+ front_matter unless [nil, ""].include?(body)
21
+ end
22
+
23
+ # @return [String] the HTML, with the front matter stripped out
24
+ def extract_html(full_markup)
25
+ return if full_markup.nil?
26
+
27
+ liquid_markup_stripped = full_markup.gsub("\n\n", "\n")
28
+ _, body = scan(liquid_markup_stripped)
29
+ body || full_markup
30
+ end
31
+
32
+ # @return [Array<String>] an array consisting of two items: the front matter
33
+ # and the HTML content.
34
+ def scan(markup)
35
+ markup = markup.gsub(/\t/, " ")
36
+ [*markup.match(/(?:---\s+(.*?)\s+---\s+)(.*\s?)$/m)&.captures]
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,52 @@
1
+ require "nokogiri"
2
+ require "liquid"
3
+
4
+ module Canvas
5
+ module Validator
6
+ class Html
7
+ LIQUID_TAG = /#{Liquid::TagStart}.*?#{Liquid::TagEnd}/om
8
+ LIQUID_VARIABLE = /#{Liquid::VariableStart}.*?#{Liquid::VariableEnd}/om
9
+ LIQUID_TAG_OR_VARIABLE = /#{LIQUID_TAG}|#{LIQUID_VARIABLE}/om
10
+
11
+ attr_reader :errors
12
+
13
+ def initialize(file)
14
+ @file = file
15
+ end
16
+
17
+ def validate
18
+ doc = Nokogiri::HTML5.fragment(extracted_html, max_errors: 1)
19
+ @errors = doc.errors
20
+ doc.errors.empty?
21
+ end
22
+
23
+ private
24
+
25
+ def extracted_html
26
+ html = strip_out_front_matter(@file)
27
+ strip_out_liquid(html)
28
+ end
29
+
30
+ # We want to strip out the liquid tags and replace them with the
31
+ # same number of empty space characters, so that the linter
32
+ # reports the correct character number.
33
+ def strip_out_liquid(html)
34
+ html.gsub(LIQUID_TAG_OR_VARIABLE) { |tag| " " * tag.size }
35
+ end
36
+
37
+ # We want to strip out the front matter and replace it
38
+ # with empty new lines, so that linter output reports the
39
+ # correct line numbers
40
+ def strip_out_front_matter(html)
41
+ extractor = Canvas::FrontMatterExtractor.new(html)
42
+
43
+ if extractor.front_matter
44
+ num_lines = extractor.front_matter.lines.size - 1
45
+ html.sub(extractor.front_matter, "\n" * num_lines)
46
+ else
47
+ html
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,43 @@
1
+ require "nokogiri"
2
+ require "liquid"
3
+
4
+ module Canvas
5
+ module Validator
6
+ class Liquid
7
+ attr_reader :errors
8
+
9
+ def initialize(file)
10
+ @file = file
11
+ end
12
+
13
+ def validate
14
+ liquid = strip_out_front_matter(@file)
15
+ ::Liquid::Template.parse(
16
+ liquid,
17
+ line_numbers: true,
18
+ error_mode: :warn
19
+ )
20
+ true
21
+ rescue ::Liquid::SyntaxError => e
22
+ @errors = [e]
23
+ false
24
+ end
25
+
26
+ private
27
+
28
+ # We want to strip out the front matter and replace it
29
+ # with empty new lines, so that linter output reports the
30
+ # correct line numbers
31
+ def strip_out_front_matter(html)
32
+ extractor = Canvas::FrontMatterExtractor.new(html)
33
+
34
+ if extractor.front_matter
35
+ num_lines = extractor.front_matter.lines.size - 1
36
+ html.sub(extractor.front_matter, "\n" * num_lines)
37
+ else
38
+ html
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,3 @@
1
+ module Canvas
2
+ VERSION = "0.1.0"
3
+ end
data/lib/canvas.rb ADDED
@@ -0,0 +1,17 @@
1
+ require_relative "canvas/version"
2
+ require_relative "canvas/cli"
3
+ require_relative "canvas/lint"
4
+ require_relative "canvas/validators/html"
5
+ require_relative "canvas/validators/liquid"
6
+ require_relative "canvas/check"
7
+ require_relative "canvas/offense"
8
+ require_relative "canvas/checks"
9
+
10
+ Dir[__dir__ + "/canvas/{checks,services}/*.rb"].each { |file| require file }
11
+
12
+ Canvas::Checks.register(Canvas::RequiredFilesCheck)
13
+ Canvas::Checks.register(Canvas::ValidHtmlCheck)
14
+ Canvas::Checks.register(Canvas::ValidLiquidCheck)
15
+
16
+ module Canvas
17
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: easol-canvas
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Byrne
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-05-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: nokogiri
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: cli-ui
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: liquid
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |
70
+ Canvas is a command line tool to help with building themes for Easol.
71
+ It provides tooling to check theme directories for errors and to make sure
72
+ they confirm with the Easol theme spec.
73
+ email: kyle@easol.com
74
+ executables:
75
+ - canvas
76
+ extensions: []
77
+ extra_rdoc_files: []
78
+ files:
79
+ - bin/canvas
80
+ - bin/rspec
81
+ - lib/canvas.rb
82
+ - lib/canvas/check.rb
83
+ - lib/canvas/checks.rb
84
+ - lib/canvas/checks/required_files_check.rb
85
+ - lib/canvas/checks/valid_html_check.rb
86
+ - lib/canvas/checks/valid_liquid_check.rb
87
+ - lib/canvas/cli.rb
88
+ - lib/canvas/lint.rb
89
+ - lib/canvas/offense.rb
90
+ - lib/canvas/services/front_matter_extractor.rb
91
+ - lib/canvas/validators/html.rb
92
+ - lib/canvas/validators/liquid.rb
93
+ - lib/canvas/version.rb
94
+ homepage:
95
+ licenses:
96
+ - MIT
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubygems_version: 3.1.6
114
+ signing_key:
115
+ specification_version: 4
116
+ summary: CLI to help with building themes for Easol
117
+ test_files: []