From 2a56c19d86b383316841c86a37cae9b0240e4d17 Mon Sep 17 00:00:00 2001 From: Arit Amana <32520970+msarit@users.noreply.github.com> Date: Tue, 1 Feb 2022 12:01:26 -0500 Subject: [PATCH] Implement Unified Embeds for Glitch URLs (#16378) * modify GlitchTag for Unified Embed * complete implementation and add specs * escape dots in regex; change capture group name * address PR review comments * rubocop fixes * update comment * pr review changes * nudge Travis * nudge Travis --- app/liquid_tags/glitch_tag.rb | 96 ++++++++++++------- .../unified_embed/registry_spec.rb | 13 +++ 2 files changed, 75 insertions(+), 34 deletions(-) diff --git a/app/liquid_tags/glitch_tag.rb b/app/liquid_tags/glitch_tag.rb index 0c0e765f1..7fe8a72cd 100644 --- a/app/liquid_tags/glitch_tag.rb +++ b/app/liquid_tags/glitch_tag.rb @@ -2,9 +2,13 @@ class GlitchTag < LiquidTagBase attr_accessor :uri PARTIAL = "liquids/glitch".freeze - ID_REGEXP = /\A[a-zA-Z0-9\-]{1,110}\z/ - TILDE_PREFIX_REGEXP = /\A~/ - OPTION_REGEXP = /(app|code|no-files|preview-first|no-attribution|file=\w(\.\w)?)/ + + REGISTRY_REGEXP = %r{https://(?:(?[\w\-]{1,110})\.)?glitch(?:\.me|\.com)(?:/edit/#!/)?(?[\w\-]{1,110})?(?\?.*)?} + ID_REGEXP = /\A(?:^~)?(?[\w\-]{1,110})\Z/ + REGEXP_OPTIONS = [REGISTRY_REGEXP, ID_REGEXP].freeze + # last part of PATH_REGEX handles line & character numbers that may appear at path end + PATH_REGEX = %r{path=(?[\w/\-.]*)[\d:]*} + OPTION_REGEXP = %r{(app|code|no-files|preview-first|no-attribution|file=([\w/\-.]+)?)} OPTIONS_TO_QUERY_PAIR = { "app" => %w[previewSize 100], "code" => %w[previewSize 0], @@ -13,10 +17,12 @@ class GlitchTag < LiquidTagBase "no-attribution" => %w[attributionHidden true] }.freeze - def initialize(_tag_name, id, _parse_context) + def initialize(_tag_name, input, _parse_context) super - @query = parse_options(id) - @id = parse_id(id) + + unescaped_input = CGI.unescape_html(input) + stripped_input = strip_tags(unescaped_input) + @id, @query = parsed_input(stripped_input) end def render(_context) @@ -31,49 +37,71 @@ class GlitchTag < LiquidTagBase private - def valid_id?(input) - (input =~ ID_REGEXP)&.zero? + def parsed_input(input) + id, *options = input.split + match = pattern_match_for(id, REGEXP_OPTIONS) + raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_glitch_id") unless match + + [get_slug(match), parse_options(options, match)] end - def parse_id(input) - id = input.split.first - id.sub!(TILDE_PREFIX_REGEXP, "") - raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_glitch_id") unless valid_id?(id) - - id + def get_slug(match) + if match_has_named_capture_group?(match, "subdomain") + match[:subdomain] + else + match[:slug] + end end - def valid_option(option) - option.match(OPTION_REGEXP) + def match_has_named_capture_group?(match, group_name) + match.names.include?(group_name) + end + + def parse_options(options, match) + # 'app' and 'code' should cancel each other out + options -= %w[app code] if options.include?("app") && options.include?("code") + + # check for file= in options, then path= in params; fallback is file=index.html + file_option = options.detect { |option| option.start_with?("file=") } + options += ["file=#{path_within_params(match)}"] unless file_option + + validated_options = options.select { |option| valid_option?(option) } + raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_options") if validated_options.empty? + + build_options(validated_options) + end + + def path_within_params(match) + return "index.html" unless match_has_named_capture_group?(match, "params") + + return "index.html" if match[:params].blank? + + path_match = pattern_match_for(match[:params], [PATH_REGEX]) + return "index.html" if path_match.blank? + + path_match[:path] + end + + def valid_option?(option) + option.match?(OPTION_REGEXP) end def build_options(options) # Convert options to query param pairs params = options.filter_map { |option| OPTIONS_TO_QUERY_PAIR[option] } - # Deal with the file option if present or use default - file_option = options.detect { |option| option.start_with?("file=") } - path = file_option ? (file_option.sub! "file=", "") : "index.html" + # by this point, there is always a 'file=' + path = options + .detect { |option| option.start_with?("file=") } + .delete_prefix("file=") + params.push ["path", path] # Encode the resulting pairs as a query string URI.encode_www_form(params) end - - def parse_options(input) - _, *options = input.split - - # 'app' and 'code' should cancel each other out - options -= %w[app code] if (options & %w[app code]) == %w[app code] - - # Validation - validated_options = options.filter_map { |option| valid_option(option) } - unless options.empty? || !validated_options.empty? - raise StandardError, I18n.t("liquid_tags.glitch_tag.invalid_options") - end - - build_options(options) - end end Liquid::Template.register_tag("glitch", GlitchTag) + +UnifiedEmbed.register(GlitchTag, regexp: GlitchTag::REGISTRY_REGEXP) diff --git a/spec/liquid_tags/unified_embed/registry_spec.rb b/spec/liquid_tags/unified_embed/registry_spec.rb index 9ced21cdb..08a2ba8b2 100644 --- a/spec/liquid_tags/unified_embed/registry_spec.rb +++ b/spec/liquid_tags/unified_embed/registry_spec.rb @@ -22,6 +22,12 @@ RSpec.describe UnifiedEmbed::Registry do "https://app.codesandbox.io/embed/exciting-knuth-hywlv?file=/index.html&runonclick=0&view=editor", ] + valid_glitch_url_formats = [ + "https://zircon-quixotic-attraction.glitch.me", + "https://glitch.com/edit/#!/zircon-quixotic-attraction", + "https://glitch.com/edit/#!/zircon-quixotic-attraction?path=script.js:1:0", + ] + valid_medium_url_formats = [ "https://medium.com/@edisonywh/my-ruby-journey-hooking-things-up-91d757e1c59c", "https://themobilist.medium.com/is-universal-basic-mobility-the-route-to-a-sustainable-c-b18e1e2d014c", @@ -106,6 +112,13 @@ RSpec.describe UnifiedEmbed::Registry do .to eq(GistTag) end + it "returns GlitchTag for a valid glitch url", :aggregate_failures do + valid_glitch_url_formats.each do |url| + expect(described_class.find_liquid_tag_for(link: url)) + .to eq(GlitchTag) + end + end + it "returns AsciinemaTag for an asciinema url" do expect(described_class.find_liquid_tag_for(link: "https://asciinema.org/a/330532")) .to eq(AsciinemaTag)