guard-jekyll-plus 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rspec +2 -0
- data/.rubocop.yml +10 -0
- data/.rubocop_todo.yml +32 -0
- data/.ruby-gemset +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +6 -2
- data/Gemfile +12 -2
- data/Guardfile +30 -0
- data/README.md +5 -5
- data/Rakefile +9 -1
- data/guard-jekyll-plus.gemspec +20 -15
- data/lib/guard/jekyll_plus.rb +54 -0
- data/lib/guard/jekyll_plus/builder.rb +46 -0
- data/lib/guard/jekyll_plus/builder/action.rb +126 -0
- data/lib/guard/jekyll_plus/builder/adder.rb +23 -0
- data/lib/guard/jekyll_plus/builder/modifier.rb +23 -0
- data/lib/guard/jekyll_plus/builder/rebuilder.rb +37 -0
- data/lib/guard/jekyll_plus/builder/remover.rb +24 -0
- data/lib/guard/jekyll_plus/config.rb +131 -0
- data/lib/guard/jekyll_plus/server.rb +111 -0
- data/lib/guard/jekyll_plus/templates/Guardfile +4 -0
- data/lib/guard/{jekyll-plus → jekyll_plus}/version.rb +1 -1
- data/spec/lib/guard/jekyll-plus/builder/adder_spec.rb +94 -0
- data/spec/lib/guard/jekyll-plus/builder/modifier_spec.rb +113 -0
- data/spec/lib/guard/jekyll-plus/builder/rebuilder_spec.rb +76 -0
- data/spec/lib/guard/jekyll-plus/builder/remover_spec.rb +97 -0
- data/spec/lib/guard/jekyll-plus/builder_spec.rb +57 -0
- data/spec/lib/guard/jekyll-plus/config_spec.rb +138 -0
- data/spec/lib/guard/jekyll-plus/server_spec.rb +79 -0
- data/spec/lib/guard/jekyll-plus_spec.rb +114 -0
- data/spec/spec_helper.rb +44 -0
- data/test/Guardfile +3 -4
- metadata +70 -14
- data/lib/guard/jekyll-plus.rb +0 -300
- data/lib/guard/jekyll-plus/templates/Guardfile +0 -5
- data/test/Gemfile +0 -3
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'guard/jekyll_plus/builder/action'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class JekyllPlus < Plugin
|
5
|
+
class Builder
|
6
|
+
class Adder < Action
|
7
|
+
def initialize(*args)
|
8
|
+
@msg = 'Files added: '
|
9
|
+
@mark = ' + '.green
|
10
|
+
@name = 'copy'
|
11
|
+
@activity = 'copying'
|
12
|
+
@color = :green
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_update(files)
|
17
|
+
header(files)
|
18
|
+
files.each { |file| copy(file, destination_path(file)) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'guard/jekyll_plus/builder/action'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class JekyllPlus < Plugin
|
5
|
+
class Builder
|
6
|
+
class Modifier < Action
|
7
|
+
def initialize(*args)
|
8
|
+
@msg = 'Files changed: '
|
9
|
+
@mark = ' ~ '.yellow
|
10
|
+
@name = 'update'
|
11
|
+
@activity = 'updating'
|
12
|
+
@color = :green
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_update(files)
|
17
|
+
header(files)
|
18
|
+
files.each { |file| copy(file, destination_path(file)) }
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'guard/jekyll_plus/builder/action'
|
4
|
+
|
5
|
+
module Guard
|
6
|
+
class JekyllPlus < Plugin
|
7
|
+
class Builder
|
8
|
+
class Rebuilder < Action
|
9
|
+
def initialize(*args)
|
10
|
+
@name = 'build'
|
11
|
+
@activity = 'building...'
|
12
|
+
@color = :yellow
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def update
|
17
|
+
header(nil)
|
18
|
+
benchmark { @site.process }
|
19
|
+
|
20
|
+
# rescue almost everything because Jekyll::Convertible forwards
|
21
|
+
# every plugin-specific exception it encounters
|
22
|
+
rescue StandardError => e
|
23
|
+
@config.error "#{@name} has failed"
|
24
|
+
@config.error e.to_s
|
25
|
+
throw :task_has_failed
|
26
|
+
end
|
27
|
+
|
28
|
+
def benchmark
|
29
|
+
elapsed = Benchmark.realtime { yield }.round(2)
|
30
|
+
change = format('%s → %s', @config.source, @config.destination)
|
31
|
+
msg = format('build completed in %ss '.green + change, elapsed)
|
32
|
+
@config.info msg
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'guard/jekyll_plus/builder/action'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class JekyllPlus < Plugin
|
5
|
+
class Builder
|
6
|
+
class Remover < Action
|
7
|
+
def initialize(*args)
|
8
|
+
@msg = 'Files removed: '
|
9
|
+
@mark = ' x '.red
|
10
|
+
@name = 'remove'
|
11
|
+
@activity = 'removing'
|
12
|
+
@color = :red
|
13
|
+
super
|
14
|
+
end
|
15
|
+
|
16
|
+
def do_update(files)
|
17
|
+
return if files.none? { |f| File.exist?(f) }
|
18
|
+
header(files)
|
19
|
+
files.each { |file| remove(destination_path(file)) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'jekyll'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
class JekyllPlus < Plugin
|
5
|
+
class Config
|
6
|
+
EXTS = %w(md mkd mkdn markdown textile html haml slim xml yml sass scss)
|
7
|
+
|
8
|
+
def initialize(options)
|
9
|
+
@options = {
|
10
|
+
extensions: [],
|
11
|
+
config: ['_config.yml'],
|
12
|
+
serve: false,
|
13
|
+
rack_config: nil,
|
14
|
+
drafts: false,
|
15
|
+
future: false,
|
16
|
+
config_hash: nil,
|
17
|
+
silent: false,
|
18
|
+
msg_prefix: 'Jekyll '
|
19
|
+
}.merge(options)
|
20
|
+
|
21
|
+
@jekyll_config = load_config(@options)
|
22
|
+
|
23
|
+
@source = local_path(@jekyll_config['source'])
|
24
|
+
@destination = local_path(@jekyll_config['destination'])
|
25
|
+
@msg_prefix = @options[:msg_prefix]
|
26
|
+
|
27
|
+
# Convert array of extensions into a regex for matching file extensions
|
28
|
+
# eg, /\.md$|\.markdown$|\.html$/i
|
29
|
+
#
|
30
|
+
extensions = @options[:extensions].concat(EXTS).flatten.uniq
|
31
|
+
extensions.map! { |e| Regexp.quote(e.sub(/^\./, '')) }
|
32
|
+
@extensions = /\.(?:#{extensions.join('|')})$/i
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :extensions
|
36
|
+
attr_reader :destination
|
37
|
+
attr_reader :jekyll_config
|
38
|
+
|
39
|
+
def server_options
|
40
|
+
jekyll_config
|
41
|
+
end
|
42
|
+
|
43
|
+
def config_file?(file)
|
44
|
+
@options[:config].include?(file)
|
45
|
+
end
|
46
|
+
|
47
|
+
def reload
|
48
|
+
@jekyll_config = load_config(@options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def info(msg)
|
52
|
+
Compat::UI.info(@msg_prefix + msg) unless silent?
|
53
|
+
end
|
54
|
+
|
55
|
+
def error(msg)
|
56
|
+
Compat::UI.error(@msg_prefix + msg)
|
57
|
+
end
|
58
|
+
|
59
|
+
def source
|
60
|
+
@jekyll_config['source']
|
61
|
+
end
|
62
|
+
|
63
|
+
def serve?
|
64
|
+
@options[:serve]
|
65
|
+
end
|
66
|
+
|
67
|
+
def host
|
68
|
+
@jekyll_config['host']
|
69
|
+
end
|
70
|
+
|
71
|
+
def baseurl
|
72
|
+
@jekyll_config['baseurl']
|
73
|
+
end
|
74
|
+
|
75
|
+
def port
|
76
|
+
@jekyll_config['port']
|
77
|
+
end
|
78
|
+
|
79
|
+
def rack_config
|
80
|
+
@options[:rack_config]
|
81
|
+
end
|
82
|
+
|
83
|
+
def rack_environment
|
84
|
+
silent? ? nil : 'development'
|
85
|
+
end
|
86
|
+
|
87
|
+
alias_method :server_root, :destination
|
88
|
+
alias_method :jekyll_serve_options, :jekyll_config
|
89
|
+
|
90
|
+
def excluded?(path)
|
91
|
+
@jekyll_config['exclude'].any? { |glob| File.fnmatch?(glob, path) }
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def silent?
|
97
|
+
@options[:silent] || @options['silent']
|
98
|
+
end
|
99
|
+
|
100
|
+
def load_config(options)
|
101
|
+
config = ::Jekyll.configuration(jekyllize_options(options))
|
102
|
+
|
103
|
+
# Override configuration with guard option values
|
104
|
+
config['show_drafts'] = options[:drafts]
|
105
|
+
config['future'] = options[:future]
|
106
|
+
config
|
107
|
+
end
|
108
|
+
|
109
|
+
def jekyllize_options(options)
|
110
|
+
opts = options.dup
|
111
|
+
return opts[:config_hash] if opts[:config_hash]
|
112
|
+
return opts unless opts[:config]
|
113
|
+
opts[:config] = [opts[:config]] unless opts[:config].is_a? Array
|
114
|
+
opts
|
115
|
+
end
|
116
|
+
|
117
|
+
def local_path(path)
|
118
|
+
# TODO: what is this for?
|
119
|
+
Dir.chdir('.')
|
120
|
+
|
121
|
+
current = Dir.pwd
|
122
|
+
path = path.sub current, ''
|
123
|
+
if path == ''
|
124
|
+
'./'
|
125
|
+
else
|
126
|
+
path.sub(/^\//, '')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'jekyll'
|
2
|
+
|
3
|
+
require 'guard/compat/plugin'
|
4
|
+
|
5
|
+
require 'guard/jekyll_plus/config'
|
6
|
+
|
7
|
+
module Guard
|
8
|
+
class JekyllPlus < Plugin
|
9
|
+
class Server
|
10
|
+
def initialize(config)
|
11
|
+
@thread = nil
|
12
|
+
@config = config
|
13
|
+
@pid = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def start
|
17
|
+
@rack = rack_available?
|
18
|
+
@rack ? start_rack : start_jekyll
|
19
|
+
|
20
|
+
msg = 'watching and serving using %s at %s:%s%s'
|
21
|
+
type = @rack ? 'rack' : 'jekyll'
|
22
|
+
msg = format(msg, type, @config.host, @config.port, @config.baseurl)
|
23
|
+
@config.info msg
|
24
|
+
end
|
25
|
+
|
26
|
+
def stop
|
27
|
+
@rack ? stop_rack : stop_jekyll
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def rack_available?
|
33
|
+
@use_rack ||= begin
|
34
|
+
Kernel.require 'rack'
|
35
|
+
true
|
36
|
+
rescue LoadError
|
37
|
+
false
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def local_config
|
42
|
+
File.exist?('config.ru') ? 'config.ru' : nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_config
|
46
|
+
(Pathname(__FILE__).expand_path.dirname + '../../rack/config.ru').to_s
|
47
|
+
end
|
48
|
+
|
49
|
+
def config_file
|
50
|
+
(@config.rack_config || local_config || default_config)
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_jekyll
|
54
|
+
# NOTE: must use process for Jekyll, because:
|
55
|
+
#
|
56
|
+
# 1) webrat has a shutdown that needs to be called. Since Jekyll
|
57
|
+
# doesn't expose the server instance, the only way to call it is to
|
58
|
+
# send "INT" to the process - but that also causes Pygments to crash
|
59
|
+
# (because it's opening a pipe with Mentos)
|
60
|
+
#
|
61
|
+
# 2) you can't use the 'detach' option of Jekyll, because you don't
|
62
|
+
# have access to the pid, because it's only logged and never returned
|
63
|
+
#
|
64
|
+
# You'll likely get a "Couldn't cleanly terminate all actors" error,
|
65
|
+
# because Celluloid doesn't gracefully handle forking.
|
66
|
+
#
|
67
|
+
fail "Server already running at: #{@pid.inspect}" unless @pid.nil?
|
68
|
+
@pid = Process.fork do
|
69
|
+
::Jekyll::Commands::Serve.process(@config.jekyll_serve_options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def start_rack
|
74
|
+
fail 'already running!' unless @pid.nil?
|
75
|
+
# Run rack in a process, because the only way to shut down Webrick
|
76
|
+
# cleanly is through calling shutdown(), and Rack sets up and INT
|
77
|
+
# handler specifically to do this - so we need to call INT.
|
78
|
+
#
|
79
|
+
# Webrick needs to shutdown
|
80
|
+
# explicitly - and that's handled by Rack (it traps INT),
|
81
|
+
ENV['RACK_ROOT'] = @config.server_root
|
82
|
+
s = ::Rack::Server.new(config: config_file,
|
83
|
+
Port: @config.port,
|
84
|
+
Host: @config.host,
|
85
|
+
environment: @config.rack_environment)
|
86
|
+
|
87
|
+
@config.info "Using: #{s.server} as server"
|
88
|
+
|
89
|
+
thin = s.server == Rack::Handler::Thin
|
90
|
+
Thin::Logging.silent = @config.rack_environment.nil? if thin
|
91
|
+
|
92
|
+
@pid = Process.fork { s.start }
|
93
|
+
end
|
94
|
+
|
95
|
+
def stop_rack
|
96
|
+
stop_pid
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop_jekyll
|
100
|
+
stop_pid
|
101
|
+
end
|
102
|
+
|
103
|
+
def stop_pid
|
104
|
+
return if @pid.nil?
|
105
|
+
Process.kill('INT', @pid)
|
106
|
+
Process.wait(@pid)
|
107
|
+
@pid = nil
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'guard/jekyll_plus/builder/adder'
|
2
|
+
|
3
|
+
module Guard
|
4
|
+
RSpec.describe JekyllPlus::Builder::Adder do
|
5
|
+
let(:site) { instance_double(Jekyll::Site) }
|
6
|
+
let(:config) { instance_double(JekyllPlus::Config) }
|
7
|
+
subject { described_class.new(config, site) }
|
8
|
+
|
9
|
+
describe '#update' do
|
10
|
+
let(:extensions) { /\.haml$/i }
|
11
|
+
|
12
|
+
before do
|
13
|
+
# for build
|
14
|
+
allow(config).to receive(:info)
|
15
|
+
allow(config).to receive(:source)
|
16
|
+
allow(config).to receive(:destination)
|
17
|
+
|
18
|
+
allow(config).to receive(:extensions).and_return(extensions)
|
19
|
+
allow(FileUtils).to receive(:mkdir_p)
|
20
|
+
allow(FileUtils).to receive(:cp)
|
21
|
+
allow($stdout).to receive(:puts)
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'when source files are added' do
|
25
|
+
it 'builds' do
|
26
|
+
expect(site).to receive(:process)
|
27
|
+
subject.update(%w(foo.haml))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when assets change' do
|
32
|
+
before do
|
33
|
+
allow(config).to receive(:destination).and_return('bar/')
|
34
|
+
allow(config).to receive(:excluded?).with('foo.jpg').and_return(false)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'copies files' do
|
38
|
+
expect(FileUtils).to receive(:cp).with('foo.jpg', 'bar/foo.jpg')
|
39
|
+
subject.update(%w(foo.jpg))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# NOTE: Jekyll just shows a message and passes the plugin,
|
44
|
+
# so it can fail with almost any possible exception.
|
45
|
+
#
|
46
|
+
# We catch StandardError to at least be somewhat reasonable
|
47
|
+
context 'when an Jekyll conversion error happens' do
|
48
|
+
before do
|
49
|
+
allow(site).to receive(:process)
|
50
|
+
.and_raise(NoMethodError, 'error evaluating Haml file')
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'shows an error' do
|
54
|
+
expect(config).to receive(:error).with('build has failed')
|
55
|
+
expect(config).to receive(:error).with(/error evaluating Haml file/)
|
56
|
+
|
57
|
+
catch(:task_has_failed) do
|
58
|
+
subject.update(%w(foo.haml))
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'throws task_has_failed symbol' do
|
63
|
+
allow(config).to receive(:error)
|
64
|
+
expect do
|
65
|
+
subject.update(%w(foo.haml))
|
66
|
+
end.to throw_symbol(:task_has_failed)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'when an error happens' do
|
71
|
+
before do
|
72
|
+
allow(config).to receive(:destination).and_return('bar/')
|
73
|
+
allow(FileUtils).to receive(:cp).and_raise(Errno::ENOENT, 'foo')
|
74
|
+
allow(config).to receive(:error)
|
75
|
+
allow(config).to receive(:excluded?).with('foo').and_return(false)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'shows an error' do
|
79
|
+
expect(config).to receive(:error).with('copy has failed')
|
80
|
+
expect(config).to receive(:error).with(/No such file.* - foo/)
|
81
|
+
catch(:task_has_failed) do
|
82
|
+
subject.update(%w(foo))
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'throws task_has_failed symbol' do
|
87
|
+
expect do
|
88
|
+
subject.update(%w(foo))
|
89
|
+
end.to throw_symbol(:task_has_failed)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|