├── script ├── test ├── release ├── bootstrap ├── cibuild └── fmt ├── .rspec ├── spec ├── fixtures │ ├── _layouts │ │ └── layout.html │ ├── _authors │ │ └── kansaichris.md │ ├── tags │ │ └── how we work.md │ ├── one_redirect_to_path.md │ ├── one_redirect_from.md │ ├── one_redirect_to_url.md │ ├── _articles │ │ ├── redirect-somewhere-else-plz.html │ │ ├── redirect-me-plz.md │ │ └── redirect-somewhere-else-im-a-permalink.html │ ├── _posts │ │ └── 2014-01-03-redirect-me-plz.md │ ├── multiple_redirect_froms.md │ └── multiple_redirect_tos.md ├── jekyll_redirect_from │ ├── context_spec.rb │ ├── layout_spec.rb │ ├── redirectable_spec.rb │ ├── generator_spec.rb │ └── redirect_page_spec.rb ├── spec_helper.rb └── integrations_spec.rb ├── lib ├── jekyll-redirect-from │ ├── version.rb │ ├── page_without_a_file.rb │ ├── context.rb │ ├── redirect.html │ ├── layout.rb │ ├── redirectable.rb │ ├── generator.rb │ └── redirect_page.rb └── jekyll-redirect-from.rb ├── Rakefile ├── Gemfile ├── .gitignore ├── .rubocop.yml ├── .rubocop_todo.yml ├── jekyll-redirect-from.gemspec ├── LICENSE.txt ├── .github └── workflows │ └── ci.yml ├── README.md └── History.markdown /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | bundle exec rspec $@ 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --format progress 3 | --require spec_helper 4 | --order random 5 | -------------------------------------------------------------------------------- /script/release: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | script/cibuild 4 | bundle exec rake release 5 | -------------------------------------------------------------------------------- /script/bootstrap: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | bundle install -j8 || bundle install 5 | -------------------------------------------------------------------------------- /spec/fixtures/_layouts/layout.html: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | LAYOUT INCLUDED 5 | 6 | {{ content }} 7 | -------------------------------------------------------------------------------- /spec/fixtures/_authors/kansaichris.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_from: /kansaichris/ 3 | --- 4 | 5 | Hi. 6 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | set -e 3 | 4 | script/test 5 | script/fmt 6 | bundle exec rake build 7 | -------------------------------------------------------------------------------- /spec/fixtures/tags/how we work.md: -------------------------------------------------------------------------------- 1 | --- 2 | redirect_to: "/tags/how-we-work/" 3 | permalink: "/tags/how we work/" 4 | --- 5 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/version.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | VERSION = "0.16.0" 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/one_redirect_to_path.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I am going somewhere external 3 | redirect_to: /foo 4 | --- 5 | 6 | Redirecting elsewhere. 7 | -------------------------------------------------------------------------------- /spec/fixtures/one_redirect_from.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I only have one redirect path 3 | redirect_from: some/other/path 4 | --- 5 | 6 | One redirect url 7 | -------------------------------------------------------------------------------- /spec/fixtures/one_redirect_to_url.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I am going somewhere external 3 | redirect_to: https://www.github.com 4 | --- 5 | 6 | Redirecting elsewhere. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_articles/redirect-somewhere-else-plz.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Please redirect away from me, sir. 3 | redirect_to: "http://www.zombo.com" 4 | --- 5 | 6 | Boo. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_articles/redirect-me-plz.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Please redirect me, sir. 3 | redirect_from: /articles/23128432159832/mary-had-a-little-lamb 4 | --- 5 | 6 | Yay. 7 | -------------------------------------------------------------------------------- /spec/fixtures/_posts/2014-01-03-redirect-me-plz.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Please redirect me, sir. 3 | redirect_from: /posts/23128432159832/mary-had-a-little-lamb 4 | --- 5 | 6 | Yay. 7 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :default => :spec 9 | -------------------------------------------------------------------------------- /spec/fixtures/_articles/redirect-somewhere-else-im-a-permalink.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: Please redirect away from me and my permalink. 3 | permalink: /tags/our projects/ 4 | redirect_to: /tags/our-projects/ 5 | --- 6 | 7 | Bye. 8 | -------------------------------------------------------------------------------- /spec/fixtures/multiple_redirect_froms.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I have lots of redirect urls 3 | redirect_from: 4 | - help 5 | - contact 6 | - let-there/be/light-he-said 7 | - /geepers/mccreepin 8 | --- 9 | 10 | Lots of redirect urls 11 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/page_without_a_file.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | class PageWithoutAFile < Jekyll::Page 5 | def read_yaml(*) 6 | @data ||= {} 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/fixtures/multiple_redirect_tos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: I have lots of redirect to urls 3 | redirect_to: 4 | - https://www.jekyllrb.com 5 | - https://www.github.com 6 | - https://www.twitter.com 7 | --- 8 | 9 | Lots of redirect to urls. 10 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | gemspec 5 | 6 | gem "github-pages" if ENV["GH_PAGES"] 7 | gem "jekyll", ENV["JEKYLL_VERSION"] if ENV["JEKYLL_VERSION"] 8 | gem "kramdown-parser-gfm" if ENV["JEKYLL_VERSION"] == "~> 3.9" 9 | -------------------------------------------------------------------------------- /script/fmt: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "RuboCop $(bundle exec rubocop --version)" 5 | bundle exec rubocop -D -E $@ 6 | success=$? 7 | if ((success != 0)); then 8 | echo -e "\nTry running \`script/fmt -a\` to automatically fix errors" 9 | fi 10 | exit $success 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | spec/fixtures/.jekyll-metadata 16 | spec/fixtures/.jekyll-cache 17 | test/tmp 18 | test/version_tmp 19 | tmp 20 | vendor/bundle 21 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/context.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | # Stubbed LiquidContext to support relative_url and absolute_url helpers 5 | class Context 6 | attr_reader :site 7 | 8 | def initialize(site) 9 | @site = site 10 | end 11 | 12 | def registers 13 | { :site => site } 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/jekyll_redirect_from/context_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe JekyllRedirectFrom::Context do 4 | subject { described_class.new(site) } 5 | 6 | it "stores the site" do 7 | expect(subject.site).to be_a(Jekyll::Site) 8 | end 9 | 10 | it "returns the register" do 11 | expect(subject.registers).to have_key(:site) 12 | expect(subject.registers[:site]).to be_a(Jekyll::Site) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Redirecting… 5 | 6 | 7 | 8 | 9 |

Redirecting…

10 | Click here if you are not redirected. 11 | 12 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/layout.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | # A stubbed layout for our default redirect template 5 | # We cannot use the standard Layout class because of site.in_source_dir 6 | class Layout < Jekyll::Layout 7 | def initialize(site) 8 | @site = site 9 | @base = __dir__ 10 | @name = "redirect.html" 11 | @path = File.expand_path(@name, @base) 12 | @relative_path = "_layouts/redirect.html" 13 | 14 | self.data = {} 15 | self.ext = "html" 16 | self.content = File.read(@path) 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | require "jekyll-redirect-from/version" 5 | require "jekyll-redirect-from/generator" 6 | 7 | module JekyllRedirectFrom 8 | # Jekyll classes which should be redirectable 9 | CLASSES = [Jekyll::Page, Jekyll::Document].freeze 10 | 11 | autoload :Context, "jekyll-redirect-from/context" 12 | autoload :RedirectPage, "jekyll-redirect-from/redirect_page" 13 | autoload :Redirectable, "jekyll-redirect-from/redirectable" 14 | autoload :Layout, "jekyll-redirect-from/layout" 15 | autoload :PageWithoutAFile, "jekyll-redirect-from/page_without_a_file" 16 | end 17 | 18 | JekyllRedirectFrom::CLASSES.each do |klass| 19 | klass.send :include, JekyllRedirectFrom::Redirectable 20 | end 21 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/redirectable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | # Module which can be mixed in to documents (and pages) to provide 5 | # redirect_to and redirect_from helpers 6 | module Redirectable 7 | # Returns a string representing the relative path or URL 8 | # to which the document should be redirected 9 | def redirect_to 10 | meta_data = to_liquid["redirect_to"] 11 | meta_data.is_a?(Array) ? meta_data.compact.first : meta_data 12 | end 13 | 14 | # Returns an array representing the relative paths to other 15 | # documents which should be redirected to this document 16 | def redirect_from 17 | meta_data = to_liquid["redirect_from"] 18 | meta_data.is_a?(Array) ? meta_data.compact : [meta_data].compact 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /spec/jekyll_redirect_from/layout_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe JekyllRedirectFrom::Layout do 4 | subject { described_class.new(@site) } 5 | 6 | it "exposes the site" do 7 | expect(subject.site).to eql(@site) 8 | end 9 | 10 | it "exposes the name" do 11 | expect(subject.name).to eql("redirect.html") 12 | end 13 | 14 | it "exposes the path" do 15 | expected = File.expand_path "../../lib/jekyll-redirect-from/redirect.html", __dir__ 16 | expect(subject.path).to eql(expected) 17 | end 18 | 19 | it "exposes the relative path" do 20 | expect(subject.relative_path).to eql("_layouts/redirect.html") 21 | end 22 | 23 | it "exposes the ext" do 24 | expect(subject.ext).to eql("html") 25 | end 26 | 27 | it "exposes data" do 28 | expect(subject.data).to eql({}) 29 | end 30 | 31 | it "exposes content" do 32 | expect(subject.content).to match("Redirecting...") 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | require: rubocop-jekyll 4 | 5 | inherit_gem: 6 | rubocop-jekyll: .rubocop.yml 7 | 8 | AllCops: 9 | TargetRubyVersion: 2.5 10 | SuggestExtensions: false 11 | Exclude: 12 | - vendor/**/* 13 | 14 | Layout/LineEndStringConcatenationIndentation: 15 | Enabled: true 16 | 17 | Lint/EmptyInPattern: 18 | Enabled: false 19 | 20 | Naming/InclusiveLanguage: 21 | Enabled: false 22 | Naming/MemoizedInstanceVariableName: 23 | Exclude: 24 | - 'lib/jekyll-redirect-from/page_without_a_file.rb' 25 | 26 | Performance/MapCompact: 27 | Enabled: true 28 | Performance/RedundantEqualityComparisonBlock: 29 | Enabled: true 30 | Performance/RedundantSplitRegexpArgument: 31 | Enabled: true 32 | 33 | Style/ConcatArrayLiterals: 34 | Exclude: 35 | - 'jekyll-redirect-from.gemspec' 36 | Style/InPatternThen: 37 | Enabled: false 38 | Style/MultilineInPatternThen: 39 | Enabled: false 40 | Style/QuotedSymbols: 41 | Enabled: true 42 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2021-09-17 12:49:45 UTC using RuboCop version 1.18.4. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | 9 | # Offense count: 3 10 | # Cop supports --auto-correct. 11 | # Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. 12 | # URISchemes: http, https 13 | Layout/LineLength: 14 | Max: 105 15 | 16 | # Offense count: 2 17 | # Configuration parameters: AllowedMethods. 18 | # AllowedMethods: enums 19 | Lint/ConstantDefinitionInBlock: 20 | Exclude: 21 | - 'spec/jekyll_redirect_from/generator_spec.rb' 22 | 23 | # Offense count: 2 24 | # Configuration parameters: AllowComments, AllowEmptyLambdas. 25 | Lint/EmptyBlock: 26 | Exclude: 27 | - 'spec/jekyll_redirect_from/generator_spec.rb' 28 | -------------------------------------------------------------------------------- /jekyll-redirect-from.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative "lib/jekyll-redirect-from/version" 4 | 5 | Gem::Specification.new do |spec| 6 | spec.name = "jekyll-redirect-from" 7 | spec.version = JekyllRedirectFrom::VERSION 8 | spec.authors = ["Parker Moore"] 9 | spec.email = ["parkrmoore@gmail.com"] 10 | spec.summary = "Seamlessly specify multiple redirection URLs for your pages and posts" 11 | spec.homepage = "https://github.com/jekyll/jekyll-redirect-from" 12 | spec.license = "MIT" 13 | 14 | spec.files = `git ls-files lib`.split("\n").concat(%w(LICENSE.txt README.md History.markdown)) 15 | spec.require_paths = ["lib"] 16 | 17 | spec.required_ruby_version = ">= 2.5.0" 18 | 19 | spec.add_runtime_dependency "jekyll", ">= 3.3", "< 5.0" 20 | 21 | spec.add_development_dependency "bundler" 22 | spec.add_development_dependency "jekyll-sitemap", "~> 1.0" 23 | spec.add_development_dependency "rake", "~> 13.0" 24 | spec.add_development_dependency "rspec", "~> 3.5" 25 | spec.add_development_dependency "rubocop-jekyll", "~> 0.13.0" 26 | end 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013-present Parker Moore and jekyll-redirect-from contributors 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "jekyll" 4 | require File.expand_path("lib/jekyll-redirect-from.rb") 5 | 6 | RSpec.configure do |config| 7 | config.run_all_when_everything_filtered = true 8 | config.filter_run :focus 9 | 10 | config.expect_with :rspec do |c| 11 | c.syntax = :expect 12 | end 13 | 14 | config.before(:each) do 15 | Jekyll.logger.log_level = :error 16 | dest_path.rmtree if dest_path.exist? 17 | site.reset 18 | end 19 | 20 | config.after(:each) do 21 | dest_path.rmtree if dest_path.exist? 22 | end 23 | 24 | def fixtures_path 25 | Pathname.new(__dir__).join("fixtures") 26 | end 27 | 28 | def dest_path 29 | Pathname.new(site.dest) 30 | end 31 | 32 | def dest_dir(*paths) 33 | dest_path.join(*paths) 34 | end 35 | 36 | def config 37 | Jekyll.configuration( 38 | "source" => fixtures_path.to_s, 39 | "destination" => fixtures_path.join("_site").to_s, 40 | "collections" => { 41 | "articles" => { "output" => true }, 42 | "authors" => {}, 43 | }, 44 | "url" => "http://jekyllrb.com", 45 | "plugins" => %w(jekyll-redirect-from jekyll-sitemap), 46 | "defaults" => [{ 47 | "scope" => { "path" => "" }, 48 | "values" => { "layout" => "layout" }, 49 | }] 50 | ) 51 | end 52 | 53 | def site 54 | @site ||= Jekyll::Site.new(config) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | ci: 13 | if: "!contains(github.event.commits[0].message, '[ci skip]')" 14 | name: 'Ruby ${{ matrix.ruby_version }}' 15 | runs-on: 'ubuntu-latest' 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | ruby_version: 21 | - "2.7" 22 | - "3.0" 23 | - "3.3" 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 5 28 | - name: "Set up Ruby ${{ matrix.ruby_version }}" 29 | uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: ${{ matrix.ruby_version }} 32 | bundler-cache: true 33 | - name: Execute tests 34 | run: bash script/test 35 | - name: Check Style Offenses 36 | run: bash script/fmt 37 | - name: Test gem build 38 | run: bundle exec gem build jekyll-redirect-from.gemspec 39 | - name: Test gem install 40 | run: bundle exec gem install jekyll-redirect-from --local --verbose 41 | 42 | ghp: 43 | if: "!contains(github.event.commits[0].message, '[ci skip]')" 44 | name: Ruby 3.3 with GitHub Pages Gem 45 | runs-on: 'ubuntu-latest' 46 | env: 47 | GH_PAGES: true 48 | strategy: 49 | fail-fast: false 50 | steps: 51 | - uses: actions/checkout@v4 52 | with: 53 | fetch-depth: 5 54 | - name: "Set up Ruby 3.3" 55 | uses: ruby/setup-ruby@v1 56 | with: 57 | ruby-version: 3.3 58 | bundler-cache: true 59 | - name: Execute tests 60 | run: bash script/test 61 | -------------------------------------------------------------------------------- /spec/jekyll_redirect_from/redirectable_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class RedirectableTestHelper 4 | include JekyllRedirectFrom::Redirectable 5 | attr_reader :to_liquid 6 | 7 | def initialize(data) 8 | @to_liquid = data 9 | end 10 | end 11 | 12 | RSpec.describe JekyllRedirectFrom::Redirectable do 13 | let(:data) { "" } 14 | subject { RedirectableTestHelper.new(data) } 15 | 16 | context "with strings" do 17 | let(:data) { { "redirect_from" => "/foo", "redirect_to" => "/bar" } } 18 | 19 | it "returns redirect_from" do 20 | expect(subject.redirect_from).to eql(["/foo"]) 21 | end 22 | 23 | it "returns redirect_to" do 24 | expect(subject.redirect_to).to eql("/bar") 25 | end 26 | end 27 | 28 | context "with arrays" do 29 | let(:data) { { "redirect_from" => ["/foo"], "redirect_to" => ["/bar"] } } 30 | 31 | it "returns redirect_from" do 32 | expect(subject.redirect_from).to eql(["/foo"]) 33 | end 34 | 35 | it "returns redirect_to" do 36 | expect(subject.redirect_to).to eql("/bar") 37 | end 38 | end 39 | 40 | context "with fields missing" do 41 | let(:data) { {} } 42 | 43 | it "returns an empty array for redirect_from" do 44 | expect(subject.redirect_from).to eql([]) 45 | end 46 | 47 | it "returns nil for redirect_to" do 48 | expect(subject.redirect_to).to be_nil 49 | end 50 | end 51 | 52 | context "with nils" do 53 | let(:data) { { "redirect_from" => nil, "redirect_to" => nil } } 54 | 55 | it "returns an empty array for redirect_from" do 56 | expect(subject.redirect_from).to eql([]) 57 | end 58 | 59 | it "returns nil for redirect_to" do 60 | expect(subject.redirect_to).to be_nil 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/generator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | class Generator < Jekyll::Generator 5 | safe true 6 | attr_reader :site, :redirects 7 | 8 | def generate(site) 9 | @site = site 10 | @redirects = {} 11 | 12 | # Inject our layout, unless the user has already specified a redirect layout' 13 | unless site.layouts.key?("redirect") 14 | site.layouts["redirect"] = JekyllRedirectFrom::Layout.new(site) 15 | end 16 | 17 | # Must duplicate pages to modify while in loop 18 | (site.docs_to_write + site.pages.dup).each do |doc| 19 | next unless redirectable_document?(doc) 20 | 21 | generate_redirect_from(doc) 22 | generate_redirect_to(doc) 23 | end 24 | 25 | generate_redirects_json if generate_redirects_json? 26 | end 27 | 28 | private 29 | 30 | # For every `redirect_from` entry, generate a redirect page 31 | def generate_redirect_from(doc) 32 | doc.redirect_from.each do |path| 33 | page = RedirectPage.redirect_from(doc, path) 34 | doc.site.pages << page 35 | redirects[page.redirect_from] = page.redirect_to 36 | end 37 | end 38 | 39 | def generate_redirect_to(doc) 40 | return unless doc.redirect_to 41 | 42 | page = RedirectPage.redirect_to(doc, doc.redirect_to) 43 | doc.data.merge!(page.data) 44 | doc.content = doc.output = page.output 45 | redirects[page.redirect_from] = page.redirect_to 46 | end 47 | 48 | def generate_redirects_json 49 | return if File.exist? site.in_source_dir("redirects.json") 50 | 51 | page = PageWithoutAFile.new(site, "", "", "redirects.json") 52 | page.content = redirects.to_json 53 | page.data["layout"] = nil 54 | site.pages << page 55 | end 56 | 57 | def redirectable_document?(doc) 58 | doc.is_a?(Jekyll::Document) || doc.is_a?(Jekyll::Page) 59 | end 60 | 61 | def generate_redirects_json? 62 | site.config.dig("redirect_from", "json") != false 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/jekyll-redirect-from/redirect_page.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module JekyllRedirectFrom 4 | # Specialty page which implements the redirect path logic 5 | class RedirectPage < Jekyll::Page 6 | # Use Jekyll's native absolute_url filter 7 | include Jekyll::Filters::URLFilters 8 | 9 | DEFAULT_DATA = { 10 | "sitemap" => false, 11 | "layout" => "redirect", 12 | }.freeze 13 | 14 | # Creates a new RedirectPage instance from a source path and redirect path 15 | # 16 | # site - The Site object 17 | # from - the (URL) path, relative to the site root to redirect from 18 | # to - the relative path or URL which the page should redirect to 19 | def self.from_paths(site, from, to) 20 | page = RedirectPage.new(site, site.source, "", "redirect.html") 21 | page.set_paths(from, to) 22 | page 23 | end 24 | 25 | # Creates a new RedirectPage instance from the path to the given doc 26 | def self.redirect_from(doc, path) 27 | RedirectPage.from_paths(doc.site, path, doc.url) 28 | end 29 | 30 | # Creates a new RedirectPage instance from the doc to the given path 31 | def self.redirect_to(doc, path) 32 | RedirectPage.from_paths(doc.site, doc.url, path) 33 | end 34 | 35 | # Overwrite the default read_yaml method since the file doesn't exist 36 | def read_yaml(_base, _name, _opts = {}) 37 | self.content = self.output = "" 38 | self.data ||= DEFAULT_DATA.dup 39 | end 40 | 41 | # Helper function to set the appropriate path metadata 42 | # 43 | # from - the relative path to the redirect page 44 | # to - the relative path or absolute URL to the redirect target 45 | def set_paths(from, to) 46 | @context ||= context 47 | from = ensure_leading_slash(from) 48 | data.merge!( 49 | "permalink" => from, 50 | "redirect" => { 51 | "from" => from, 52 | "to" => %r!^https?://!.match?(to) ? to : absolute_url(to), 53 | } 54 | ) 55 | end 56 | 57 | def redirect_from 58 | data["redirect"]["from"] if data["redirect"] 59 | end 60 | 61 | def redirect_to 62 | data["redirect"]["to"] if data["redirect"] 63 | end 64 | 65 | private 66 | 67 | def context 68 | JekyllRedirectFrom::Context.new(site) 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/integrations_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe "JekyllRedirectFrom integration tests" do 4 | before { site.process } 5 | let(:relative_path) { "" } 6 | let(:path) { dest_dir(relative_path) } 7 | let(:contents) { File.read(path) } 8 | 9 | context "pages" do 10 | context "single redirect from" do 11 | let(:relative_path) { "some/other/path.html" } 12 | 13 | it "exists in the built site" do 14 | expect(path).to exist 15 | expect(contents).to match("http://jekyllrb.com/one_redirect_from.html") 16 | end 17 | end 18 | 19 | context "multiple redirect froms" do 20 | ["help", "contact", "let-there/be/light-he-said", "geepers/mccreepin"].each do |redirect| 21 | context "the #{redirect} redirect" do 22 | let(:relative_path) { "#{redirect}.html" } 23 | 24 | it "exists in the built site" do 25 | expect(path).to exist 26 | expect(contents).to match("http://jekyllrb.com/multiple_redirect_froms.html") 27 | end 28 | end 29 | end 30 | end 31 | 32 | context "a redirect to URL" do 33 | let(:relative_path) { "one_redirect_to_url.html" } 34 | 35 | it "exists in the built site" do 36 | expect(path).to exist 37 | expect(contents).to match("https://www.github.com") 38 | end 39 | end 40 | 41 | context "a redirect to path" do 42 | let(:relative_path) { "one_redirect_to_path.html" } 43 | 44 | it "exists in the built site" do 45 | expect(path).to exist 46 | expect(contents).to match("http://jekyllrb.com/foo") 47 | end 48 | end 49 | end 50 | 51 | context "documents" do 52 | context "a single redirect from" do 53 | let(:relative_path) { "articles/23128432159832/mary-had-a-little-lamb.html" } 54 | 55 | it "exists in the built site" do 56 | expect(path).to exist 57 | expect(contents).to match("http://jekyllrb.com/articles/redirect-me-plz.html") 58 | end 59 | end 60 | 61 | context "redirect to" do 62 | let(:relative_path) { "articles/redirect-somewhere-else-plz.html" } 63 | 64 | it "exists in the built site" do 65 | expect(path).to exist 66 | expect(contents).to match("http://www.zombo.com") 67 | end 68 | end 69 | 70 | context "with a permalink" do 71 | let(:relative_path) { "tags/our projects/index.html" } 72 | 73 | it "exists in the built site" do 74 | expect(path).to exist 75 | expect(contents).to match("http://jekyllrb.com/tags/our-projects/") 76 | end 77 | end 78 | end 79 | 80 | context "sitemap" do 81 | let(:relative_path) { "sitemap.xml" } 82 | 83 | it "doesn't contain redirects" do 84 | expect(contents).to_not be_nil 85 | expect(contents).to_not match("redirect_to") 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JekyllRedirectFrom 2 | 3 | Give your Jekyll posts and pages multiple URLs. 4 | 5 | When importing your posts and pages from, say, Tumblr, it's annoying and 6 | impractical to create new pages in the proper subdirectories so they, e.g. 7 | `/post/123456789/my-slug-that-is-often-incompl`, redirect to the new post URL. 8 | 9 | Instead of dealing with maintaining those pages for redirection, let 10 | `jekyll-redirect-from` handle it for you. 11 | 12 | [![Build Status](https://github.com/jekyll/jekyll-redirect-from/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/jekyll/jekyll-redirect-from/actions/workflows/ci.yml) 13 | 14 | ## How it Works 15 | 16 | Redirects are performed by serving an HTML file with an HTTP-REFRESH meta 17 | tag which points to your destination. No `.htaccess` file, nginx conf, xml 18 | file, or anything else will be generated. It simply creates HTML files. 19 | 20 | ## Installation 21 | 22 | Add this line to your application's Gemfile: 23 | 24 | gem 'jekyll-redirect-from' 25 | 26 | And then execute: 27 | 28 | $ bundle 29 | 30 | Or install it yourself as: 31 | 32 | $ gem install jekyll-redirect-from 33 | 34 | Once it's installed into your environment, add it to your `_config.yml`: 35 | 36 | ```yaml 37 | plugins: 38 | - jekyll-redirect-from 39 | ``` 40 | 41 | 💡 If you are using a Jekyll version less than 3.5.0, use the `gems` key instead of `plugins`. 42 | 43 | If you're using Jekyll in `safe` mode to mimic GitHub Pages, make sure to 44 | add jekyll-redirect-from to your whitelist: 45 | 46 | ```yaml 47 | whitelist: 48 | - jekyll-redirect-from 49 | ``` 50 | 51 | Then run `jekyll --safe` like normal. 52 | 53 | ## Usage 54 | 55 | The objective of this gem is to allow an author to specify multiple URLs for a 56 | page, such that the alternative URLs redirect to the new Jekyll URL. 57 | 58 | To use it, simply add the array to the YAML front-matter of your page or post: 59 | 60 | ```yaml 61 | title: My amazing post 62 | redirect_from: 63 | - /post/123456789/ 64 | - /post/123456789/my-amazing-post/ 65 | ``` 66 | 67 | Redirects including a trailing slash will generate a corresponding subdirectory containing an `index.html`, while redirects without a trailing slash will generate a corresponding `filename` without an extension, and without a subdirectory. 68 | 69 | For example... 70 | 71 | ```yaml 72 | redirect_from: 73 | - /post/123456789/my-amazing-post 74 | ``` 75 | 76 | ...will generate the following page in the destination: 77 | 78 | ```text 79 | /post/123456789/my-amazing-post 80 | ``` 81 | 82 | While... 83 | 84 | ```yaml 85 | redirect_from: 86 | - /post/123456789/my-amazing-post/ 87 | ``` 88 | 89 | ...will generate the following page in the destination: 90 | 91 | ```text 92 | /post/123456789/my-amazing-post/index.html 93 | ``` 94 | 95 | These pages will contain an HTTP-REFRESH meta tag which redirect to your URL. 96 | 97 | You can also specify just **one url** like this: 98 | 99 | ```yaml 100 | title: My other awesome post 101 | redirect_from: /post/123456798/ 102 | ``` 103 | 104 | ### Prefix 105 | 106 | If `site.url` is set, its value, together with `site.baseurl`, is used as a prefix for the redirect url automatically. This is useful for scenarios where a site isn't available from the domain root, so the redirects point to the correct path. If `site.url` is not set, only `site.baseurl` is used, if set. 107 | 108 | **_Note_**: If you are hosting your Jekyll site on [GitHub Pages](https://pages.github.com/), and `site.url` is not set, the prefix is set to the pages domain name i.e. http://example.github.io/project or a custom CNAME. 109 | 110 | ### Redirect To 111 | 112 | Sometimes, you may want to redirect a site page to a totally different website. This plugin also supports that with the `redirect_to` key: 113 | 114 | ```yaml 115 | title: My amazing post 116 | redirect_to: http://www.github.com 117 | ``` 118 | 119 | **Note**: Using `redirect_to` or `redirect_from` with collections will only work with files which are output to HTML, such as `.md`, `.textile`, `.html` etc. 120 | 121 | ## Customizing the redirect template 122 | 123 | If you want to customize the redirect template, you can. Simply create a layout in your site's `_layouts` directory called `redirect.html`. 124 | 125 | Your layout will get the following variables: 126 | 127 | * `page.redirect.from` - the relative path to the redirect page 128 | * `page.redirect.to` - the absolute URL (where available) to the target page 129 | 130 | ## Configuration 131 | 132 | You can configure this plugin in `_config.yml` by adding to the `redirect_from` key. 133 | 134 | ### Disabling `redirects.json` 135 | 136 | By default, a file called `redirects.json`, which can be used for automated testing or to implement server-side redirects, will be included in the output. To exclude it from the output, set the `json` key to `false`: 137 | 138 | ```yml 139 | redirect_from: 140 | json: false 141 | ``` 142 | 143 | ## Contributing 144 | 145 | 1. Fork it 146 | 2. Create your feature branch (`git checkout -b my-new-feature`) 147 | 3. Commit your changes (`git commit -am 'Add some feature'`) 148 | 4. Push to the branch (`git push origin my-new-feature`) 149 | 5. Create new Pull Request 150 | -------------------------------------------------------------------------------- /History.markdown: -------------------------------------------------------------------------------- 1 | ## HEAD 2 | 3 | ### styles 4 | 5 | * style: Style/SpecialGlobalVars (#216) 6 | 7 | ### Development Fixes 8 | 9 | * fix Gemfile to correctly install jekyll 3.9 (#224) 10 | * Refactor specs for RedirectPage (#222) 11 | * Lock to RuboCop v1.18.x (#242) 12 | * Clean up gemspec (#243) 13 | * Bump versions in CI workflow (#273) 14 | 15 | ### Bug Fixes 16 | 17 | * Refactor Redirectable mixin to reduce allocations (#241) 18 | 19 | ## 0.16.0 / 2020-01-26 20 | 21 | ### Minor Enhancements 22 | 23 | * Allows generation of `redirects.json` to be disabled (#207) 24 | * Allow redirects from and for subclasses of page and document (#204) 25 | 26 | ### Bug Fixes 27 | 28 | * Use `Hash#key?` instead of `Hash#keys.any?` (#201) 29 | 30 | ### Development Fixes 31 | 32 | * Target Ruby 2.4 33 | * Stop testing with backwards-compatible site config (#211) 34 | 35 | ### Documentation 36 | 37 | * Simplifies YAML for `redirect_to` (#185) 38 | 39 | ## 0.15.0 / 2019-03-23 40 | 41 | ### Development Fixes 42 | 43 | * chore(deps): rubocop-jekyll 0.3 (#187) 44 | 45 | ### Bug Fixes 46 | 47 | * Allow testing and using with Jekyll 4.x (#196) 48 | 49 | ## 0.14.0 / 2018-06-29 50 | 51 | ### Minor Enhancements 52 | 53 | * Run javascript at first to avoid splash (#158) 54 | 55 | ### Development Fixes 56 | 57 | * Use Rubocop 0.57 58 | * Target Ruby 2.3 59 | * Test against Ruby 2.5 (#173) 60 | 61 | ## 0.13.0 / 2017-12-03 62 | 63 | * Test against same version of Ruby that GitHub Pages uses (#132) 64 | 65 | ### Development Fixes 66 | 67 | * Rubocop (#141) 68 | * Fix tests for jekyll 3.5.x (#160) 69 | * Rubocop: autocorrect (#165) 70 | 71 | ### Minor Enhancements 72 | 73 | * HTML encode ellipsis (#142) 74 | * Added no-index to template (#152) 75 | * Define path with __dir__ (#161) 76 | 77 | ### Major Enhancements 78 | 79 | * Create redirects.json file (#147) 80 | 81 | ### Documentation 82 | 83 | * Update README.md (#167) 84 | 85 | ## 0.12.1 / 2017-01-12 86 | 87 | ### Development Fixes 88 | 89 | * Stop testing Ruby 1.9 (#133) 90 | 91 | ### Minor Enhancements 92 | 93 | * Use send to monkey patch to support Ruby < 2.2.0 (#136) 94 | * set `page.output` to empty string instead of nil (#137) 95 | 96 | ## 0.12.0 / 2017-01-02 97 | 98 | ### Major Enhancements 99 | 100 | * Support for custom redirect templates 101 | * Use Jekyll's `absolute_url` filter to generate canonical URLs (now respecting `baseurl`) 102 | * Rely more heavily on Jekyll's native Page, permalink, and extension handling logic 103 | 104 | ### Minor Enhancementse 105 | 106 | * redirect_to Pages should not have a layout. (#115) 107 | * Require Jekyll >= 3.3 108 | 109 | ### Development Enhancements 110 | 111 | * Push redirect logic to the redirect page model (#131) 112 | * Add Rubocop and enforce Jekyll coding standards 113 | * Tests no longer build and write the entire site between each example 114 | * Removed all the `is_*`? and `has_*`? helpers from the generator 115 | 116 | ## 0.11.0 / 2016-07-06 117 | 118 | * Redirect page should not have any layout (#106) 119 | * Include absolute path in canonical url (#109) 120 | * Add tag and language (#100) 121 | * Ensure redirect_to links produce an HTML file. (#111) 122 | 123 | ## 0.10.0 / 2016-03-16 124 | 125 | * Ensure output extension is assigned (#96) 126 | 127 | ## 0.9.1 / 2015-12-11 128 | 129 | * Enforce double-quote strings to pass htmlhint (#83) 130 | * Stringify all values coming from `site.github` (#89) 131 | 132 | ## 0.9.0 / 2015-10-28 133 | 134 | * Support Jekyll 3 stable (#76) 135 | * Test against Jekyll 3, 2, and GitHub Pages (#72) 136 | 137 | ## 0.8.0 / 2015-05-20 138 | 139 | * Exclude redirect pages from sitemap (#69) 140 | 141 | ## 0.7.0 / 2015-03-16 142 | 143 | * Remove spaces in redirect page (#62) 144 | * Only parse through documents/pages/posts (#56) 145 | * Simplified `has_alt_urls?` and `has_redirect_to_url?` conditions (#52) 146 | * Add support for Jekyll 3. (#59) 147 | 148 | ## 0.6.2 / 2014-09-12 149 | 150 | * Fixed error where `redirect_to` `Document`s were not being output properly (#46) 151 | 152 | ## 0.6.1 / 2014-09-08 153 | 154 | * Fixed error when the `site.github` config key is not a `Hash` (#43) 155 | 156 | ## 0.6.0 / 2014-08-22 157 | 158 | * Support redirecting to/from collection documents (#40) 159 | 160 | ## 0.5.0 / 2014-08-10 161 | 162 | ### Minor Enhancements 163 | 164 | * Support `redirect_to` property (#32) 165 | * Automatically prefix redirects with the `baseurl` or GitHub URL. (#26) 166 | 167 | ### Bug Fixes 168 | 169 | * Remove unnecessary `Array#flatten` (#34) 170 | 171 | ### Development Fixes 172 | 173 | * Use `be_truthy` instead of `be_true`. (#33) 174 | 175 | ## 0.4.0 / 2014-05-06 176 | 177 | ### Major Enhancements 178 | 179 | * Upgrade to Jekyll 2.0 (#27) 180 | 181 | ### Minor Enhancements 182 | 183 | * Shorten resulting HTML to make redirects quicker (#20) 184 | 185 | ### Development Fixes 186 | 187 | * Use SVG Travis badge in README (#21) 188 | 189 | ## 0.3.1 / 2014-01-22 190 | 191 | ### Bug Fixes 192 | 193 | * Add `safe true` to the `Jekyll::Generator` so it can be run in safe mode (#12) 194 | 195 | ## 0.3.0 / 2014-01-15 196 | 197 | ### Major Enhancements 198 | 199 | * `redirect_from` items are now proper permalinks rooted in site source (#8) 200 | 201 | ### Development Fixes 202 | 203 | * Add forgotten `s` to `gems` in README.md (#7) 204 | 205 | ## 0.2.0 / 2014-01-04 206 | 207 | ### Minor Enhancements 208 | 209 | * Allow user to set one or many `redirect_from` URLs 210 | * Rename from `jekyll-alt-urls` to `jekyll-redirect-from` (props to @benbalter) 211 | * Namespace now its own module: `JekyllRedirectFrom` (#3) 212 | 213 | ### Development Fixes 214 | 215 | * Add history file 216 | * Add specs (#3) 217 | * Add TravisCI badge (#4) 218 | 219 | ## 0.1.0 / 2013-12-15 220 | 221 | * Birthday! 222 | -------------------------------------------------------------------------------- /spec/jekyll_redirect_from/generator_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.describe JekyllRedirectFrom::Generator do 4 | before(:each) do 5 | site.read 6 | site.generate 7 | site.render 8 | site.write 9 | end 10 | 11 | context "layouts" do 12 | context "a site with a redirect layout" do 13 | before { site.layouts["redirect"] = "foo" } 14 | 15 | it "doesn't inject the layout" do 16 | expect(site.layouts["redirect"]).to eql("foo") 17 | end 18 | end 19 | 20 | context "a site without a redirect layout" do 21 | it "injects the layout" do 22 | expect(site.layouts["redirect"]).to be_a(JekyllRedirectFrom::Layout) 23 | end 24 | end 25 | end 26 | 27 | context "redirect froms" do 28 | context "pages" do 29 | context "a page with a single redirect" do 30 | let(:page) { site.pages.find { |p| p.url == "/some/other/path" } } 31 | 32 | it "creates the redirect" do 33 | expect(page).to_not be_nil 34 | expect(page.output).to match("http://jekyllrb.com/one_redirect_from.html") 35 | end 36 | end 37 | 38 | context "a page with multiple redirects" do 39 | let(:redirects) do 40 | ["/help", "/contact", "/let-there/be/light-he-said", "/geepers/mccreepin"] 41 | end 42 | 43 | it "creates all the redirects" do 44 | redirects.each do |url| 45 | page = site.pages.find { |p| p.url == url } 46 | expect(page).to_not be_nil 47 | expect(page.output).to match("http://jekyllrb.com/multiple_redirect_froms.html") 48 | end 49 | end 50 | end 51 | end 52 | 53 | context "documents" do 54 | let(:page) { site.pages.find { |p| p.url == "/articles/23128432159832/mary-had-a-little-lamb" } } 55 | 56 | it "redirects" do 57 | expect(page).to_not be_nil 58 | expect(page.output).to match("http://jekyllrb.com/articles/redirect-me-plz.html") 59 | end 60 | end 61 | end 62 | 63 | context "redirect tos" do 64 | context "pages" do 65 | context "a single redirect to" do 66 | let(:page) { site.pages.find { |p| p.url == "/one_redirect_to_url.html" } } 67 | 68 | it "redirects" do 69 | expect(page.output).to match("https://www.github.com") 70 | end 71 | end 72 | 73 | context "multiple redirect tos" do 74 | let(:page) { site.pages.find { |p| p.url == "/multiple_redirect_tos.html" } } 75 | 76 | it "redirects to the first entry" do 77 | expect(page.output).to match("https://www.jekyllrb.com") 78 | end 79 | end 80 | end 81 | 82 | context "documents" do 83 | let(:doc) { site.documents.find { |p| p.url == "/articles/redirect-somewhere-else-plz.html" } } 84 | 85 | it "redirects" do 86 | expect(doc.output).to match("http://www.zombo.com") 87 | end 88 | end 89 | end 90 | 91 | context "redirects.json" do 92 | let(:path) { dest_dir("redirects.json") } 93 | let(:contents) { File.read(path) } 94 | let(:redirects) { JSON.parse(contents) } 95 | let(:domain) { "http://jekyllrb.com" } 96 | 97 | it "creates the redirects file" do 98 | expect(path).to exist 99 | end 100 | 101 | it "contains redirects" do 102 | expect(redirects.count).to eql(13) 103 | end 104 | 105 | it "contains single redirects tos" do 106 | expect(redirects.keys).to include "/one_redirect_to_path.html" 107 | expect(redirects["/one_redirect_to_path.html"]).to eql("#{domain}/foo") 108 | end 109 | 110 | it "contains multiple redirect tos" do 111 | expect(redirects.keys).to include "/multiple_redirect_tos.html" 112 | expect(redirects["/multiple_redirect_tos.html"]).to eql("https://www.jekyllrb.com") 113 | end 114 | 115 | it "contains single redirect froms" do 116 | expect(redirects.keys).to include "/some/other/path" 117 | expect(redirects["/some/other/path"]).to eql("#{domain}/one_redirect_from.html") 118 | end 119 | 120 | it "contains multiple redirect froms" do 121 | expect(redirects.keys).to include "/help" 122 | expect(redirects["/help"]).to eql("#{domain}/multiple_redirect_froms.html") 123 | 124 | expect(redirects.keys).to include "/contact" 125 | expect(redirects["/contact"]).to eql("#{domain}/multiple_redirect_froms.html") 126 | end 127 | 128 | context "with a user-supplied redirects.json" do 129 | let(:source_path) { File.join fixtures_path, "redirects.json" } 130 | before do 131 | File.write source_path, { "foo" => "bar" }.to_json 132 | site.reset 133 | site.read 134 | site.generate 135 | site.render 136 | site.write 137 | end 138 | 139 | after do 140 | FileUtils.rm_f source_path 141 | end 142 | 143 | it "doesn't overwrite redirects.json" do 144 | expect(path).to exist 145 | expect(redirects).to eql("foo" => "bar") 146 | end 147 | end 148 | 149 | context "when explicitly disabled" do 150 | let(:site) { Jekyll::Site.new(config.merge("redirect_from" => { "json" => false })) } 151 | 152 | it "does not create the redirects file" do 153 | expect(path).to_not exist 154 | end 155 | end 156 | end 157 | 158 | context "redirectable_document?" do 159 | let(:generator) { JekyllRedirectFrom::Generator.new } 160 | 161 | it "accepts subclasses of Jekyll::Document" do 162 | SubclassOfJekyllDocument = Class.new(Jekyll::Document) { define_method(:initialize) {} } 163 | expect(generator.send(:redirectable_document?, SubclassOfJekyllDocument.new)).to be_truthy 164 | end 165 | 166 | it "accepts subclasses of Jekyll::Page" do 167 | SubclassOfJekyllPage = Class.new(Jekyll::Page) { define_method(:initialize) {} } 168 | expect(generator.send(:redirectable_document?, SubclassOfJekyllPage.new)).to be_truthy 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /spec/jekyll_redirect_from/redirect_page_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | describe JekyllRedirectFrom::RedirectPage do 4 | let(:site_url) { site.config["url"] } 5 | before { site.read } 6 | 7 | shared_examples "a redirect page" do 8 | context "being a page" do 9 | before { page.read_yaml(nil, nil, nil) } 10 | 11 | it "returns no content" do 12 | expect(page.content).to eql("") 13 | end 14 | 15 | it "returns no output" do 16 | expect(page.output).to eql("") 17 | end 18 | 19 | it "sets default data" do 20 | expect(page.to_liquid["layout"]).to eql("redirect") 21 | expect(page.to_liquid["sitemap"]).to be_falsey 22 | end 23 | 24 | it "sets the paths" do 25 | expect(page.to_liquid["permalink"]).to eql(from) 26 | expect(page.to_liquid).to have_key("redirect") 27 | expect(page.to_liquid["redirect"]["from"]).to eql(from) 28 | expect(page.to_liquid["redirect"]["to"]).to eql("#{site_url}#{to}") 29 | end 30 | 31 | it "sets the permalink" do 32 | expect(page.to_liquid["permalink"]).to eql(from) 33 | end 34 | 35 | it "sets redirect metadata" do 36 | expect(page.to_liquid).to have_key("redirect") 37 | expect(page.to_liquid["redirect"]["from"]).to eql(from) 38 | expect(page.to_liquid["redirect"]["to"]).to eql("#{site_url}#{to}") 39 | end 40 | end 41 | 42 | context "generating" do 43 | before { site.generate } 44 | let(:output) { Jekyll::Renderer.new(site, page, site.site_payload).run } 45 | 46 | it "renders the template" do 47 | expect(output).to_not be_nil 48 | expect(output.to_s).to_not be_empty 49 | end 50 | 51 | it "contains the meta refresh tag" do 52 | expect(output).to match("") 53 | end 54 | 55 | it "contains the javascript redirect" do 56 | expect(output).to match("") 57 | end 58 | 59 | it "contains canonical link in header" do 60 | expect(output).to match("") 61 | end 62 | 63 | it "contains the clickable link" do 64 | expect(output).to match("Click here if you are not redirected.") 65 | end 66 | end 67 | end 68 | 69 | context "redirecting to" do 70 | let(:to) { "/bar" } 71 | let(:doc) { site.documents.first } 72 | subject(:page) { described_class.redirect_to(doc, to) } 73 | let(:from) { doc.url } 74 | 75 | it_behaves_like "a redirect page" 76 | 77 | context "a relative path" do 78 | let(:to) { "/bar" } 79 | 80 | it "redirects" do 81 | expect(page.to_liquid["permalink"]).to eql("/2014/01/03/redirect-me-plz.html") 82 | expect(page.to_liquid).to have_key("redirect") 83 | expect(page.to_liquid["redirect"]["to"]).to eql("#{site_url}#{to}") 84 | expect(page.to_liquid["redirect"]["from"]).to eql("/2014/01/03/redirect-me-plz.html") 85 | end 86 | 87 | context "with no leading slash" do 88 | let(:to) { "bar" } 89 | 90 | it "redirects" do 91 | expect(page.to_liquid).to have_key("redirect") 92 | expect(page.to_liquid["redirect"]["to"]).to eql("#{site_url}/#{to}") 93 | end 94 | end 95 | 96 | context "with a trailing slash" do 97 | let(:to) { "/bar/" } 98 | 99 | it "redirects" do 100 | expect(page.to_liquid).to have_key("redirect") 101 | expect(page.to_liquid["redirect"]["to"]).to eql("#{site_url}#{to}") 102 | end 103 | end 104 | end 105 | 106 | context "an absolute URL" do 107 | let(:to) { "https://foo.invalid" } 108 | 109 | it "redirects" do 110 | expect(page.to_liquid["permalink"]).to eql("/2014/01/03/redirect-me-plz.html") 111 | expect(page.to_liquid).to have_key("redirect") 112 | expect(page.to_liquid["redirect"]["to"]).to eql("https://foo.invalid") 113 | expect(page.to_liquid["redirect"]["from"]).to eql("/2014/01/03/redirect-me-plz.html") 114 | end 115 | end 116 | end 117 | 118 | context "redirecting from" do 119 | let(:from) { "/foo" } 120 | let(:doc) { site.documents.first } 121 | subject(:page) { described_class.redirect_from(doc, from) } 122 | let(:to) { doc.url } 123 | 124 | it_behaves_like "a redirect page" 125 | 126 | it "sets liquid data for the page" do 127 | expect(page.to_liquid["permalink"]).to eql(from) 128 | expect(page.to_liquid).to have_key("redirect") 129 | expect(page.to_liquid["redirect"]["from"]).to eql(from) 130 | expected = "http://jekyllrb.com/2014/01/03/redirect-me-plz.html" 131 | expect(page.to_liquid["redirect"]["to"]).to eql(expected) 132 | end 133 | 134 | context "when redirect from has no extension" do 135 | let(:from) { "/foo" } 136 | 137 | it "adds .html" do 138 | expected = File.expand_path "foo.html", site.dest 139 | expect(page.destination("/")).to eql(expected) 140 | end 141 | end 142 | 143 | context "when redirect from is a directory" do 144 | let(:from) { "/foo/" } 145 | 146 | it "knows to add the index.html" do 147 | expected = File.expand_path "foo/index.html", site.dest 148 | expect(page.destination("/")).to eql(expected) 149 | end 150 | 151 | it "uses HTML" do 152 | expect(page.output_ext).to eql(".html") 153 | end 154 | end 155 | 156 | context "when redirect from is an HTML file" do 157 | let(:from) { "/foo.html" } 158 | 159 | it "adds .html" do 160 | expected = File.expand_path "foo.html", site.dest 161 | expect(page.destination("/")).to eql(expected) 162 | end 163 | end 164 | 165 | context "when redirect from is another extension" do 166 | let(:from) { "/foo.htm" } 167 | 168 | it "doesn't add .html" do 169 | expected = File.expand_path "foo.htm", site.dest 170 | expect(page.destination("/")).to eql(expected) 171 | end 172 | 173 | it "honors the extension" do 174 | expect(page.output_ext).to eql(".htm") 175 | end 176 | end 177 | 178 | context "when redirect from has no leading slash" do 179 | let(:from) { "foo" } 180 | 181 | it "adds the slash" do 182 | expected = File.expand_path "foo.html", site.dest 183 | expect(page.destination("/")).to eql(expected) 184 | end 185 | 186 | it "uses HTML" do 187 | expect(page.output_ext).to eql(".html") 188 | end 189 | end 190 | end 191 | end 192 | --------------------------------------------------------------------------------