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.
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