jekyll-remote-theme 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8229e0bedfef52dba41d17eff8eb8e4bba23972c
4
+ data.tar.gz: 3f0cb3209952df98c3d7db8fac598590f83e6fcc
5
+ SHA512:
6
+ metadata.gz: 75fef7047c3d3f2d5a6727e53a6079ca45f45d6e3340f769d95d4f4874c89df97735e905d6f9b59eca401aae8057b5ac06859bbd0a884818bcf933a115bdcf63
7
+ data.tar.gz: 68995b0baa3a99fc41682e74ac5a6c4cdb953a0b814461e03cac599dad36ff26f4c5d107994ac06ebd62cbff4ae76621aa114a18b890025b67a560d25b9f9b57
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require "fileutils"
5
+ require "tempfile"
6
+ require "addressable"
7
+
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+
10
+ module Jekyll
11
+ module RemoteTheme
12
+ autoload :Downloader, "jekyll-remote-theme/downloader"
13
+ autoload :Executor, "jekyll-remote-theme/executor"
14
+ autoload :MockGemspec, "jekyll-remote-theme/mock_gemspec"
15
+ autoload :Munger, "jekyll-remote-theme/munger"
16
+ autoload :Theme, "jekyll-remote-theme/theme"
17
+ autoload :VERSION, "jekyll-remote-theme/version"
18
+
19
+ CONFIG_KEY = "remote_theme".freeze
20
+ LOG_KEY = "Remote Theme:".freeze
21
+
22
+ def self.init(site)
23
+ Munger.new(site).munge!
24
+ end
25
+ end
26
+ end
27
+
28
+ Jekyll::Hooks.register :site, :after_reset do |site|
29
+ Jekyll::RemoteTheme.init(site)
30
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module RemoteTheme
5
+ class Downloader
6
+ include RemoteTheme::Executor
7
+
8
+ HOST = "https://codeload.github.com".freeze
9
+ PROJECT_URL = "https://github.com/benbalter/jekyll-remote-theme".freeze
10
+ USER_AGENT = "Jekyll Remote Theme/#{VERSION} (+#{PROJECT_URL})".freeze
11
+ TEMP_PREFIX = "jekyll-remote-theme-".freeze
12
+
13
+ attr_reader :theme
14
+ private :theme
15
+
16
+ def initialize(theme)
17
+ @theme = theme
18
+ end
19
+
20
+ def run
21
+ if downloaded?
22
+ Jekyll.logger.debug LOG_KEY, "Using existing #{theme.name_with_owner}"
23
+ return
24
+ end
25
+
26
+ download
27
+ unzip
28
+ set_theme_root
29
+
30
+ @downloaded = true
31
+ end
32
+
33
+ def downloaded?
34
+ @downloaded ||= theme_dir_exists? && !theme_dir_empty?
35
+ end
36
+
37
+ def temp_dir
38
+ @temp_dir ||= File.realpath Dir.mktmpdir(TEMP_PREFIX)
39
+ end
40
+
41
+ private
42
+
43
+ def zip_file
44
+ @zip_file ||= Tempfile.new([TEMP_PREFIX, ".zip"])
45
+ end
46
+
47
+ def download
48
+ Jekyll.logger.debug LOG_KEY, "Downloading #{zip_url} to #{zip_file.path}"
49
+ cmd = [
50
+ *timeout_command, "curl", "--url", zip_url, "--output", zip_file.path,
51
+ "--user-agent", USER_AGENT, "--fail", "--silent", "--show-error",
52
+ ]
53
+ run_command(*cmd)
54
+ end
55
+
56
+ def unzip
57
+ Jekyll.logger.debug LOG_KEY, "Unzipping #{zip_file.path} to #{temp_dir}"
58
+ cmd = [*timeout_command, "unzip", zip_file.path, "-d", temp_dir]
59
+ run_command(*cmd)
60
+ zip_file.unlink
61
+ end
62
+
63
+ # Codeload generated zip files contain a top level folder in the form of
64
+ # THEME_NAME-GIT_REF/. While requests for Git repos are case incensitive,
65
+ # the zip subfolder will respect the case in the repository's name, thus
66
+ # making it impossible to predict the true path to the theme. In case we're
67
+ # on a case-sensitive file system, set the theme's root to the true theme
68
+ # directory, after we've extracted the zip and can determine its actual path.
69
+ def set_theme_root
70
+ theme.root = Dir["#{temp_dir}/*"].first
71
+ Jekyll.logger.debug LOG_KEY, "Setting theme root to #{theme.root}"
72
+ end
73
+
74
+ # Full URL to codeload zip download endpoint for the given theme
75
+ def zip_url
76
+ Addressable::URI.join(
77
+ HOST, "#{theme.owner}/", "#{theme.name}/", "zip/", theme.git_ref
78
+ ).normalize
79
+ end
80
+
81
+ def theme_dir_exists?
82
+ theme.root && Dir.exist?(theme.root)
83
+ end
84
+
85
+ def theme_dir_empty?
86
+ Dir["#{theme.root}/*"].empty?
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Jekyll
6
+ module RemoteTheme
7
+ module Executor
8
+ class ExecutionError < StandardError; end
9
+
10
+ TIMEOUT = 3600
11
+ TIMEOUT_COMMANDS = %w(timeout gtimeout).freeze
12
+
13
+ def run_command(*cmds)
14
+ stdout, stderr, status = Open3.capture3(*cmds)
15
+
16
+ if status.exitstatus != 0
17
+ Jekyll.logger.error LOG_KEY, "Error running command #{cmds.join(" ")}"
18
+ Jekyll.logger.debug LOG_KEY, stdout
19
+ raise ExecutionError, stderr
20
+ end
21
+
22
+ stdout
23
+ end
24
+
25
+ def timeout_command
26
+ @timeout_command ||= begin
27
+ cmd = TIMEOUT_COMMANDS.find { |exe| executable_exists?(exe) }
28
+ cmd ? [cmd, TIMEOUT.to_s].freeze : [].freeze
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def executable_exists?(executable)
35
+ ENV["PATH"].split(File::PATH_SEPARATOR).any? do |dir|
36
+ exe = File.join(dir, executable)
37
+ File.file?(exe) && File.executable?(exe)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module RemoteTheme
5
+ # Jekyll::Theme expects the theme's gemspec to tell it things like
6
+ # the path to the theme and runtime dependencies. MockGemspec serves as a
7
+ # stand in, since remote themes don't have Gemspecs
8
+ class MockGemspec
9
+ extend Forwardable
10
+ def_delegator :theme, :root, :full_gem_path
11
+
12
+ def initialize(theme)
13
+ @theme = theme
14
+ end
15
+
16
+ def runtime_dependencies
17
+ []
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :theme
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module RemoteTheme
5
+ class Munger
6
+ extend Forwardable
7
+ def_delegator :site, :config
8
+ attr_reader :site
9
+
10
+ def initialize(site)
11
+ @site = site
12
+ end
13
+
14
+ def munge!
15
+ return unless raw_theme
16
+
17
+ unless theme.valid?
18
+ Jekyll.logger.error LOG_KEY, "#{raw_theme.inspect} is not a valid remote theme"
19
+ return
20
+ end
21
+
22
+ Jekyll.logger.info LOG_KEY, "Using theme #{theme.name_with_owner}"
23
+ return if munged?
24
+
25
+ downloader.run
26
+ configure_theme
27
+ enqueue_theme_cleanup
28
+
29
+ theme
30
+ end
31
+
32
+ private
33
+
34
+ def munged?
35
+ site.theme && site.theme.is_a?(Jekyll::RemoteTheme::Theme)
36
+ end
37
+
38
+ def theme
39
+ @theme ||= Theme.new(raw_theme)
40
+ end
41
+
42
+ def raw_theme
43
+ config[CONFIG_KEY]
44
+ end
45
+
46
+ def downloader
47
+ @downloader ||= Downloader.new(theme)
48
+ end
49
+
50
+ def configure_theme
51
+ return unless theme
52
+ site.config["theme"] = theme.name
53
+ site.theme = theme
54
+ site.theme.configure_sass
55
+ site.send(:configure_include_paths)
56
+ end
57
+
58
+ def enqueue_theme_cleanup
59
+ at_exit do
60
+ return unless munged? && downloader.downloaded?
61
+ Jekyll.logger.debug LOG_KEY, "Cleaning up #{downloader.temp_dir}"
62
+ FileUtils.rm_rf downloader.temp_dir
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module RemoteTheme
5
+ class Theme < Jekyll::Theme
6
+ OWNER_REGEX = %r!(?<owner>[a-z0-9\-]+)!i
7
+ NAME_REGEX = %r!(?<name>[a-z0-9\-_]+)!i
8
+ REF_REGEX = %r!@(?<ref>[a-z0-9\._\-]+)!i # May be a branch, tag, or commit
9
+ THEME_REGEX = %r!\A#{OWNER_REGEX}/#{NAME_REGEX}(?:#{REF_REGEX})?\z!i
10
+
11
+ # Initializes a new Jekyll::RemoteTheme::Theme
12
+ #
13
+ # raw_theme can be in the form of:
14
+ #
15
+ # 1. owner/theme-name - a GitHub owner + theme-name string
16
+ # 2. owner/theme-name@git_ref - a GitHub owner + theme-name + Git ref string
17
+ def initialize(raw_theme)
18
+ @raw_theme = raw_theme.to_s.downcase.strip
19
+ end
20
+
21
+ def name
22
+ theme_parts[:name]
23
+ end
24
+
25
+ def owner
26
+ theme_parts[:owner]
27
+ end
28
+
29
+ def name_with_owner
30
+ [owner, name].join("/")
31
+ end
32
+ alias_method :nwo, :name_with_owner
33
+
34
+ def valid?
35
+ theme_parts && name && owner
36
+ end
37
+
38
+ def git_ref
39
+ theme_parts[:ref] || "master"
40
+ end
41
+
42
+ # Override Jekyll::Theme's native #root which calls gemspec.full_gem_path
43
+ def root
44
+ defined?(@root) ? @root : nil
45
+ end
46
+
47
+ def root=(path)
48
+ @root = File.realpath(path)
49
+ end
50
+
51
+ def inspect
52
+ "#<Jekyll::RemoteTheme::Theme owner=\"#{owner}\" name=\"#{name}\"" \
53
+ " ref=\"#{git_ref}\" root=\"#{root}\">"
54
+ end
55
+
56
+ private
57
+
58
+ def theme_parts
59
+ @theme_parts ||= @raw_theme.match(THEME_REGEX)
60
+ end
61
+
62
+ def gemspec
63
+ @gemspec ||= MockGemspec.new(self)
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Jekyll
4
+ module RemoteTheme
5
+ VERSION = "0.1.0".freeze
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,121 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: jekyll-remote-theme
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ben Balter
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-10-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: jekyll-theme-primer
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rubocop
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.4'
83
+ description:
84
+ email:
85
+ - ben.balter@github.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - lib/jekyll-remote-theme.rb
91
+ - lib/jekyll-remote-theme/downloader.rb
92
+ - lib/jekyll-remote-theme/executor.rb
93
+ - lib/jekyll-remote-theme/mock_gemspec.rb
94
+ - lib/jekyll-remote-theme/munger.rb
95
+ - lib/jekyll-remote-theme/theme.rb
96
+ - lib/jekyll-remote-theme/version.rb
97
+ homepage: https://github.com/benbalter/jekyll-remote-theme
98
+ licenses:
99
+ - MIT
100
+ metadata: {}
101
+ post_install_message:
102
+ rdoc_options: []
103
+ require_paths:
104
+ - lib
105
+ required_ruby_version: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ requirements: []
116
+ rubyforge_project:
117
+ rubygems_version: 2.6.11
118
+ signing_key:
119
+ specification_version: 4
120
+ summary: Jekyll plugin for building Jekyll sites with any GitHub-hosted theme
121
+ test_files: []