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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +2 -0
  3. data/.rubocop.yml +10 -0
  4. data/.rubocop_todo.yml +32 -0
  5. data/.ruby-gemset +1 -0
  6. data/.travis.yml +6 -0
  7. data/CHANGELOG.md +6 -2
  8. data/Gemfile +12 -2
  9. data/Guardfile +30 -0
  10. data/README.md +5 -5
  11. data/Rakefile +9 -1
  12. data/guard-jekyll-plus.gemspec +20 -15
  13. data/lib/guard/jekyll_plus.rb +54 -0
  14. data/lib/guard/jekyll_plus/builder.rb +46 -0
  15. data/lib/guard/jekyll_plus/builder/action.rb +126 -0
  16. data/lib/guard/jekyll_plus/builder/adder.rb +23 -0
  17. data/lib/guard/jekyll_plus/builder/modifier.rb +23 -0
  18. data/lib/guard/jekyll_plus/builder/rebuilder.rb +37 -0
  19. data/lib/guard/jekyll_plus/builder/remover.rb +24 -0
  20. data/lib/guard/jekyll_plus/config.rb +131 -0
  21. data/lib/guard/jekyll_plus/server.rb +111 -0
  22. data/lib/guard/jekyll_plus/templates/Guardfile +4 -0
  23. data/lib/guard/{jekyll-plus → jekyll_plus}/version.rb +1 -1
  24. data/spec/lib/guard/jekyll-plus/builder/adder_spec.rb +94 -0
  25. data/spec/lib/guard/jekyll-plus/builder/modifier_spec.rb +113 -0
  26. data/spec/lib/guard/jekyll-plus/builder/rebuilder_spec.rb +76 -0
  27. data/spec/lib/guard/jekyll-plus/builder/remover_spec.rb +97 -0
  28. data/spec/lib/guard/jekyll-plus/builder_spec.rb +57 -0
  29. data/spec/lib/guard/jekyll-plus/config_spec.rb +138 -0
  30. data/spec/lib/guard/jekyll-plus/server_spec.rb +79 -0
  31. data/spec/lib/guard/jekyll-plus_spec.rb +114 -0
  32. data/spec/spec_helper.rb +44 -0
  33. data/test/Guardfile +3 -4
  34. metadata +70 -14
  35. data/lib/guard/jekyll-plus.rb +0 -300
  36. data/lib/guard/jekyll-plus/templates/Guardfile +0 -5
  37. 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,4 @@
1
+ guard 'jekyll_plus' do
2
+ watch(/.*/)
3
+ ignore(/^_site/)
4
+ end
@@ -1,5 +1,5 @@
1
1
  module Guard
2
2
  class JekyllPlusVersion
3
- VERSION = "2.0.0"
3
+ VERSION = '2.0.1'
4
4
  end
5
5
  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