jekyll-remote-theme-0.4.1/0000755000004100000410000000000013561646730015442 5ustar www-datawww-datajekyll-remote-theme-0.4.1/jekyll-remote-theme.gemspec0000644000004100000410000000626013561646730022676 0ustar www-datawww-data######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: jekyll-remote-theme 0.4.1 ruby lib Gem::Specification.new do |s| s.name = "jekyll-remote-theme".freeze s.version = "0.4.1" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Ben Balter".freeze] s.date = "2019-10-21" s.email = ["ben.balter@github.com".freeze] s.files = ["lib/jekyll-remote-theme.rb".freeze, "lib/jekyll-remote-theme/downloader.rb".freeze, "lib/jekyll-remote-theme/mock_gemspec.rb".freeze, "lib/jekyll-remote-theme/munger.rb".freeze, "lib/jekyll-remote-theme/theme.rb".freeze, "lib/jekyll-remote-theme/version.rb".freeze] s.homepage = "https://github.com/benbalter/jekyll-remote-theme".freeze s.licenses = ["MIT".freeze] s.required_ruby_version = Gem::Requirement.new(">= 2.3.0".freeze) s.rubygems_version = "2.5.2.1".freeze s.summary = "Jekyll plugin for building Jekyll sites with any GitHub-hosted theme".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q.freeze, ["~> 2.0"]) s.add_runtime_dependency(%q.freeze, ["< 5.0", ">= 3.5"]) s.add_development_dependency(%q.freeze, ["~> 0.5"]) s.add_development_dependency(%q.freeze, ["~> 0.2"]) s.add_development_dependency(%q.freeze, ["~> 0.11"]) s.add_development_dependency(%q.freeze, ["~> 3.0"]) s.add_development_dependency(%q.freeze, ["~> 0.59"]) s.add_development_dependency(%q.freeze, ["~> 0.3"]) s.add_runtime_dependency(%q.freeze, [">= 1.3.0"]) s.add_development_dependency(%q.freeze, ["~> 3.0"]) else s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["< 5.0", ">= 3.5"]) s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 0.2"]) s.add_dependency(%q.freeze, ["~> 0.11"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 0.59"]) s.add_dependency(%q.freeze, ["~> 0.3"]) s.add_dependency(%q.freeze, [">= 1.3.0"]) s.add_dependency(%q.freeze, ["~> 3.0"]) end else s.add_dependency(%q.freeze, ["~> 2.0"]) s.add_dependency(%q.freeze, ["< 5.0", ">= 3.5"]) s.add_dependency(%q.freeze, ["~> 0.5"]) s.add_dependency(%q.freeze, ["~> 0.2"]) s.add_dependency(%q.freeze, ["~> 0.11"]) s.add_dependency(%q.freeze, ["~> 3.0"]) s.add_dependency(%q.freeze, ["~> 0.59"]) s.add_dependency(%q.freeze, ["~> 0.3"]) s.add_dependency(%q.freeze, [">= 1.3.0"]) s.add_dependency(%q.freeze, ["~> 3.0"]) end end jekyll-remote-theme-0.4.1/lib/0000755000004100000410000000000013561646730016210 5ustar www-datawww-datajekyll-remote-theme-0.4.1/lib/jekyll-remote-theme.rb0000644000004100000410000000147713561646730022431 0ustar www-datawww-data# frozen_string_literal: true require "jekyll" require "fileutils" require "tempfile" require "addressable" require "net/http" require "zip" $LOAD_PATH.unshift(File.dirname(__FILE__)) module Jekyll module RemoteTheme class DownloadError < StandardError; end autoload :Downloader, "jekyll-remote-theme/downloader" autoload :MockGemspec, "jekyll-remote-theme/mock_gemspec" autoload :Munger, "jekyll-remote-theme/munger" autoload :Theme, "jekyll-remote-theme/theme" autoload :VERSION, "jekyll-remote-theme/version" CONFIG_KEY = "remote_theme" LOG_KEY = "Remote Theme:" TEMP_PREFIX = "jekyll-remote-theme-" def self.init(site) Munger.new(site).munge! end end end Jekyll::Hooks.register :site, :after_reset do |site| Jekyll::RemoteTheme.init(site) end jekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/0000755000004100000410000000000013561646730022073 5ustar www-datawww-datajekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/version.rb0000644000004100000410000000014213561646730024102 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module RemoteTheme VERSION = "0.4.1" end end jekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/downloader.rb0000644000004100000410000000674213561646730024567 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module RemoteTheme class Downloader PROJECT_URL = "https://github.com/benbalter/jekyll-remote-theme" USER_AGENT = "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})" MAX_FILE_SIZE = 1 * (1024 * 1024 * 1024) # Size in bytes (1 GB) NET_HTTP_ERRORS = [ Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::OpenTimeout, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, ].freeze def initialize(theme) @theme = theme end def run if downloaded? Jekyll.logger.debug LOG_KEY, "Using existing #{theme.name_with_owner}" return end download unzip end def downloaded? @downloaded ||= theme_dir_exists? && !theme_dir_empty? end private attr_reader :theme def zip_file @zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"], :binmode => true) end def download Jekyll.logger.debug LOG_KEY, "Downloading #{zip_url} to #{zip_file.path}" Net::HTTP.start(zip_url.host, zip_url.port, :use_ssl => true) do |http| http.request(request) do |response| raise_unless_sucess(response) enforce_max_file_size(response.content_length) response.read_body do |chunk| zip_file.write chunk end end end @downloaded = true rescue *NET_HTTP_ERRORS => e raise DownloadError, e.message end def request return @request if defined? @request @request = Net::HTTP::Get.new zip_url.request_uri @request["User-Agent"] = USER_AGENT @request end def raise_unless_sucess(response) return if response.is_a?(Net::HTTPSuccess) raise DownloadError, "#{response.code} - #{response.message}" end def enforce_max_file_size(size) return unless size && size > MAX_FILE_SIZE raise DownloadError, "Maximum file size of #{MAX_FILE_SIZE} bytes exceeded" end def unzip Jekyll.logger.debug LOG_KEY, "Unzipping #{zip_file.path} to #{theme.root}" # File IO is already open, rewind pointer to start of file to read zip_file.rewind Zip::File.open(zip_file) do |archive| archive.each { |file| file.extract path_without_name_and_ref(file.name) } end ensure zip_file.close zip_file.unlink end # Full URL to codeload zip download endpoint for the given theme def zip_url @zip_url ||= Addressable::URI.new( :scheme => theme.scheme, :host => "codeload.#{theme.host}", :path => [theme.owner, theme.name, "zip", theme.git_ref].join("/") ).normalize end def theme_dir_exists? theme.root && Dir.exist?(theme.root) end def theme_dir_empty? Dir["#{theme.root}/*"].empty? end # Codeload generated zip files contain a top level folder in the form of # THEME_NAME-GIT_REF/. While requests for Git repos are case insensitive, # the zip subfolder will respect the case in the repository's name, thus # making it impossible to predict the true path to the theme. In case we're # on a case-sensitive file system, strip the parent folder from all paths. def path_without_name_and_ref(path) Jekyll.sanitized_path theme.root, path.split("/").drop(1).join("/") end end end end jekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/mock_gemspec.rb0000644000004100000410000000262513561646730025061 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module RemoteTheme # Jekyll::Theme expects the theme's gemspec to tell it things like # the path to the theme and runtime dependencies. MockGemspec serves as a # stand in, since remote themes don't need Gemspecs class MockGemspec extend Forwardable def_delegator :theme, :root, :full_gem_path DEPENDENCY_PREFIX = %r!^\s*[a-z]+\.add_(?:runtime_)?dependency!.freeze DEPENDENCY_REGEX = %r!#{DEPENDENCY_PREFIX}\(?\s*["']([a-z_-]+)["']!.freeze def initialize(theme) @theme = theme end def runtime_dependencies @runtime_dependencies ||= dependency_names.map do |name| Gem::Dependency.new(name) end end private def contents @contents ||= File.read(path, :encoding => "utf-8") if path end def path @path ||= potential_paths.find { |path| File.exist? path } end def potential_paths [theme.name, "jekyll-theme-#{theme.name}"].map do |filename| File.expand_path "#{filename}.gemspec", theme.root end end def dependency_names @dependency_names ||= if contents contents.scan(DEPENDENCY_REGEX).flatten else [] end end attr_reader :theme end end end jekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/theme.rb0000644000004100000410000000515413561646730023527 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module RemoteTheme class Theme < Jekyll::Theme OWNER_REGEX = %r!(?[a-z0-9\-]+)!i.freeze NAME_REGEX = %r!(?[a-z0-9\._\-]+)!i.freeze REF_REGEX = %r!@(?[a-z0-9\._\-]+)!i.freeze # May be a branch, tag, or commit THEME_REGEX = %r!\A#{OWNER_REGEX}/#{NAME_REGEX}(?:#{REF_REGEX})?\z!i.freeze # Initializes a new Jekyll::RemoteTheme::Theme # # raw_theme can be in the form of: # # 1. owner/theme-name - a GitHub owner + theme-name string # 2. owner/theme-name@git_ref - a GitHub owner + theme-name + Git ref string # 3. http[s]://github..com/owner/theme-name # - An enterprise GitHub instance + a GitHub owner + a theme-name string # 4. http[s]://github..com/owner/theme-name@git_ref # - An enterprise GitHub instance + a GitHub owner + a theme-name + Git ref string def initialize(raw_theme) @raw_theme = raw_theme.to_s.downcase.strip super(@raw_theme) end def name theme_parts[:name] end def owner theme_parts[:owner] end def host uri&.host end def scheme uri&.scheme end def name_with_owner [owner, name].join("/") end alias_method :nwo, :name_with_owner def valid? return false unless uri && theme_parts && name && owner host && valid_hosts.include?(host) end def git_ref theme_parts[:ref] || "master" end def root @root ||= File.realpath Dir.mktmpdir(TEMP_PREFIX) end def inspect "#" end private def uri return @uri if defined? @uri @uri = if @raw_theme =~ THEME_REGEX Addressable::URI.new( :scheme => "https", :host => "github.com", :path => @raw_theme ) else Addressable::URI.parse @raw_theme end rescue Addressable::URI::InvalidURIError @uri = nil end def theme_parts @theme_parts ||= uri.path[1..-1].match(THEME_REGEX) if uri end def gemspec @gemspec ||= MockGemspec.new(self) end def valid_hosts @valid_hosts ||= [ "github.com", ENV["PAGES_GITHUB_HOSTNAME"], ENV["GITHUB_HOSTNAME"], ].compact.to_set end end end end jekyll-remote-theme-0.4.1/lib/jekyll-remote-theme/munger.rb0000644000004100000410000000265313561646730023723 0ustar www-datawww-data# frozen_string_literal: true module Jekyll module RemoteTheme class Munger extend Forwardable def_delegator :site, :config attr_reader :site def initialize(site) @site = site end def munge! return unless raw_theme unless theme.valid? Jekyll.logger.error LOG_KEY, "#{raw_theme.inspect} is not a valid remote theme" return end Jekyll.logger.info LOG_KEY, "Using theme #{theme.name_with_owner}" return theme if munged? downloader.run configure_theme enqueue_theme_cleanup theme end private def munged? site.theme&.is_a?(Jekyll::RemoteTheme::Theme) end def theme @theme ||= Theme.new(raw_theme) end def raw_theme config[CONFIG_KEY] end def downloader @downloader ||= Downloader.new(theme) end def configure_theme return unless theme site.config["theme"] = theme.name site.theme = theme site.theme.configure_sass site.send(:configure_include_paths) site.plugin_manager.require_theme_deps end def enqueue_theme_cleanup at_exit do return unless munged? && downloader.downloaded? Jekyll.logger.debug LOG_KEY, "Cleaning up #{theme.root}" FileUtils.rm_rf theme.root end end end end end