guard-jekyll-plus 2.0.0 → 2.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|