easol-canvas 0.1.0

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