hawkins 0.1.0 → 2.0.0

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.
data/lib/hawkins/cli.rb DELETED
@@ -1,190 +0,0 @@
1
- require 'jekyll'
2
- require 'guard'
3
- require 'guard/runner'
4
- require 'listen'
5
-
6
- module Hawkins
7
- class Cli < Thor
8
- include Thor::Actions
9
-
10
- attr_accessor :jekyll_config
11
-
12
- def self.exit_on_failure?
13
- true
14
- end
15
-
16
- def initialize(*args)
17
- super
18
- Jekyll.logger.log_level = :warn
19
- @jekyll_config = Jekyll::Configuration[Jekyll::Configuration::DEFAULTS]
20
- @jekyll_config.read_config_files(@jekyll_config.config_files({}))
21
- end
22
-
23
- desc "post TITLE", "create a post"
24
- option :editor, :default => ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
25
- option :date, :default => Time.now.to_s
26
- def post(title)
27
- begin
28
- date = Date.parse(options[:date])
29
- rescue
30
- # If an exception is not descended from the Thor::Error class, Thor
31
- # prints the stacktrace. We don't want a stacktrace in this instance,
32
- # so we lie and use one of Thor's error classes in the call to fail().
33
- fail(Thor::InvocationError, "ERROR: Could not parse '#{options[:date]}' as a date")
34
- end
35
- slug = title.to_url
36
- dest = path_to(:_posts)
37
-
38
- shell.mute { empty_directory(dest) }
39
-
40
- filename = "#{date.strftime('%Y-%m-%d')}-#{slug}.md"
41
- content = <<-CONTENT.gsub(/^\s*/, '')
42
- ---
43
- title: #{title}
44
- ---
45
- CONTENT
46
-
47
- create_file(path_to(dest, filename), content)
48
-
49
- case options[:editor]
50
- when /g?vim/
51
- editor_args = "+"
52
- when /x?emacs/
53
- editor_args = "+#{content.lines.count}"
54
- else
55
- editor_args = nil
56
- end
57
-
58
- exec(*[options[:editor], editor_args, path_to(dest, filename)].compact)
59
- end
60
-
61
- desc "isolate FILE ...", "work on a file or files in isolation (globs are allowed)"
62
- long_desc <<-LONGDESC
63
- Jekyll's regeneration capability is limited to regenerating the
64
- entire site. This option will ignore all files except the ones you
65
- specify in order to speed regeneration.
66
-
67
- Keep in mind that Jekyll's inclusion mechanism is not aware of
68
- subdirectories so this command operates on the basename of all files
69
- that match the file or glob you provide.
70
- LONGDESC
71
- option :future, :type => :boolean, :default => false, :desc => "publish future dated posts"
72
- option :drafts, :type => :boolean, :default => false, :desc => "publish draft posts"
73
- def isolate(*files)
74
- SafeYAML::OPTIONS[:default_mode] = :safe
75
-
76
- pages = []
77
- pages << Hawkins::DEFAULT_EXTENSIONS.map do |ext|
78
- Dir.glob("**/*.#{ext}")
79
- end
80
- pages.flatten!.reject! { |f| File.fnmatch?('_site/*', f) }
81
-
82
- pages.select! do |p|
83
- content = File.read(p)
84
- # Jekyll only renders pages with YAML frontmatter. See Jekyll's
85
- # convertible.rb read_yaml method.
86
- content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
87
- end
88
-
89
- isolation_config = {}
90
- isolation_config['exclude'] = pages.concat(jekyll_config['exclude'])
91
- isolation_config['include'] = Hawkins::DEFAULT_INCLUDES
92
-
93
- files.each do |glob|
94
- matches = Dir.glob(glob)
95
- matches.map! do |f|
96
- ext = File.extname(f)
97
- # If we have to add to this list from Rack, then Rack will have no
98
- # idea what the original file extension was. Use a wildcard to
99
- # avoid this issue.
100
- "#{File.basename(f, ext)}.*"
101
- end
102
- if matches.empty?
103
- raise Thor::Error.new("Could not find any matches for #{glob}.")
104
- end
105
- isolation_config['include'] += matches
106
- end
107
-
108
- isolation_config['include'].uniq!
109
-
110
- # We have to write the isolation config out to a file so that the Guard
111
- # can detect when the configuration changes.
112
- create_file(Hawkins::ISOLATION_FILE, YAML.dump(isolation_config))
113
-
114
- begin
115
- invoke(:serve, [], options)
116
- ensure
117
- remove_file(Hawkins::ISOLATION_FILE)
118
- end
119
- end
120
-
121
- desc "serve", "render and serve the site"
122
- option :future, :type => :boolean, :default => false, :desc => "publish future dated posts"
123
- option :drafts, :type => :boolean, :default => false, :desc => "publish draft posts"
124
- def serve
125
- config_files = jekyll_config.config_files({}) || []
126
- if File.exist?(Hawkins::ISOLATION_FILE)
127
- config_files << Hawkins::ISOLATION_FILE
128
- end
129
-
130
- # By default Jekyll uses the absolute path and Guard doesn't so we need to fix that.
131
- dest = Pathname.new(jekyll_config['destination']).relative_path_from(Pathname.new(Dir.pwd))
132
-
133
- contents = <<-GUARDFILE.gsub(/^\s*/, '')
134
- interactor :off
135
- notification :off
136
- guard 'hawkins',
137
- :config => #{config_files},
138
- :drafts => #{options[:drafts]},
139
- :future => #{options[:future]} do
140
- watch %r{.*}
141
- ignore %r{^#{dest}}
142
- end
143
-
144
- guard 'livereload',
145
- :grace_period => 5 do
146
- watch %r{.*}
147
- end
148
- GUARDFILE
149
- guard_start(contents)
150
- end
151
-
152
- # Methods in the no_tasks block are not exposed to users
153
- no_tasks do
154
- def guard_start(guardfile_contents)
155
- Guard.setup(:guardfile_contents => guardfile_contents)
156
- Guard::Runner.new.run(:start)
157
- Guard.listener.start
158
-
159
- exitcode = 0
160
- begin
161
- while Guard.interactor.foreground != :exit
162
- Guard.queue.process while Guard.queue.pending?
163
- end
164
- rescue Interrupt
165
- rescue SystemExit => e
166
- exitcode = e.status
167
- end
168
-
169
- guard_stop
170
- exitcode
171
- end
172
-
173
- def guard_stop
174
- Guard.listener.stop
175
- Guard.interactor.background
176
- Guard::Runner.new.run(:stop)
177
- end
178
-
179
- def self.source_root
180
- File.expand_path("..", File.dirname(__FILE__))
181
- end
182
-
183
- # Builds a path within the source_root
184
- def path_to(*args)
185
- args = args.map(&:to_s)
186
- File.join(*args)
187
- end
188
- end
189
- end
190
- end
data/lib/hawkins/guard.rb DELETED
@@ -1,268 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- require 'benchmark'
4
- require 'guard/plugin'
5
- require 'hawkins'
6
- require 'jekyll'
7
- require 'thin'
8
-
9
- # Most of this is courtesy of the guard-jekyll-plus gem at
10
- # https://github.com/imathis/guard-jekyll-plus
11
-
12
- module Guard
13
- class Hawkins < Plugin
14
- def initialize(options={})
15
- super
16
-
17
- default_extensions = ::Hawkins::DEFAULT_EXTENSIONS
18
-
19
- @options = {
20
- :extensions => [],
21
- :config => Jekyll::Configuration.new.config_files({}),
22
- :drafts => false,
23
- :future => false,
24
- :config_hash => nil,
25
- :silent => false,
26
- :msg_prefix => 'Jekyll'
27
- }.merge(options)
28
-
29
- @config = load_config(@options)
30
- @source = local_path(@config['source'])
31
- @destination = local_path(@config['destination'])
32
- @msg_prefix = @options[:msg_prefix]
33
- @app_prefix = 'Hawkins'
34
-
35
- # Convert array of extensions into a regex for matching file extensions
36
- # E.g. /\.md$|\.markdown$|\.html$/i
37
- extensions = @options[:extensions].concat(default_extensions).flatten.uniq
38
- @extensions = Regexp.new(
39
- extensions.map { |e| (e << '$').gsub('\.', '\\.') }.join('|'),
40
- true
41
- )
42
-
43
- # set Jekyll server thread to nil
44
- @server_thread = nil
45
-
46
- # Create a Jekyll site
47
- @site = Jekyll::Site.new(@config)
48
- end
49
-
50
- def load_config(options)
51
- config = jekyll_config(options)
52
-
53
- # Override configuration with option values
54
- config['show_drafts'] ||= options[:drafts]
55
- config['future'] ||= options[:future]
56
- config
57
- end
58
-
59
- def reload_config!
60
- UI.info "Reloading Jekyll configuration!"
61
- @config = load_config(@options)
62
- end
63
-
64
- def start
65
- build
66
- start_server
67
- return if @config[:silent]
68
- msg = "#{@app_prefix} "
69
- msg += "watching and serving at #{@config['host']}:#{@config['port']}#{@config['baseurl']}"
70
- UI.info(msg)
71
- end
72
-
73
- def reload
74
- stop if !@server_thread.nil? && @server_thread.alive?
75
- reload_config!
76
- start
77
- end
78
-
79
- def reload_server
80
- stop_server
81
- start_server
82
- end
83
-
84
- def stop
85
- stop_server
86
- end
87
-
88
- def run_on_modifications(paths)
89
- # At this point we know @options[:config] is going to be an Array
90
- # thanks to the call to jekyll_config earlier.
91
- reload_config! if @options[:config].map { |f| paths.include?(f) }.any?
92
- matched = jekyll_matches paths
93
- unmatched = non_jekyll_matches paths
94
-
95
- if matched.size > 0
96
- build(matched, "Files changed: ", " ~ ".yellow)
97
- elsif unmatched.size > 0
98
- copy(unmatched)
99
- end
100
- end
101
-
102
- def run_on_additions(paths)
103
- matched = jekyll_matches paths
104
- unmatched = non_jekyll_matches paths
105
-
106
- if matched.size > 0
107
- build(matched, "Files added: ", " + ".green)
108
- elsif unmatched.size > 0
109
- copy(unmatched)
110
- end
111
- end
112
-
113
- def run_on_removals(paths)
114
- matched = jekyll_matches paths
115
- unmatched = non_jekyll_matches paths
116
-
117
- if matched.size > 0
118
- build(matched, "Files removed: ", " x ".red)
119
- elsif unmatched.size > 0
120
- remove(unmatched)
121
- end
122
- end
123
-
124
- private
125
-
126
- def build(files=nil, message='', mark=nil)
127
- UI.info "#{@msg_prefix} #{message}" + "building...".yellow unless @config[:silent]
128
- if files
129
- puts '| '
130
- files.each { |file| puts '|' + mark + file }
131
- puts '| '
132
- end
133
- elapsed = Benchmark.realtime { Jekyll::Site.new(@config).process }
134
- unless @config[:silent]
135
- msg = "#{@msg_prefix} " + "build completed in #{elapsed.round(2)}s ".green
136
- msg += "#{@source} → #{@destination}"
137
- UI.info(msg)
138
- end
139
- rescue
140
- UI.error("#{@msg_prefix} build has failed") unless @config[:silent]
141
- stop_server
142
- throw :task_has_failed
143
- end
144
-
145
- # Copy static files to destination directory
146
- #
147
- def copy(files=[])
148
- files = ignore_stitch_sources files
149
- return false unless files.size > 0
150
- begin
151
- message = 'copied file'
152
- message += 's' if files.size > 1
153
- UI.info "#{@msg_prefix} #{message.green}" unless @config[:silent]
154
- puts '| '
155
- files.each do |file|
156
- path = destination_path file
157
- FileUtils.mkdir_p(File.dirname(path))
158
- FileUtils.cp(file, path)
159
- puts '|' + " → ".green + path
160
- end
161
- puts '| '
162
-
163
- rescue
164
- UI.error "#{@msg_prefix} copy has failed" unless @config[:silent]
165
- UI.error e
166
- stop_server
167
- throw :task_has_failed
168
- end
169
- true
170
- end
171
-
172
- # Remove deleted source file/directories from destination
173
- def remove(files=[])
174
- # Ensure at least one file still exists (other scripts may clean up too)
175
- return false unless files.select { |f| File.exist? f }.size > 0
176
- begin
177
- message = 'removed file'
178
- message += 's' if files.size > 1
179
- UI.info "#{@msg_prefix} #{message.red}" unless @config[:silent]
180
- puts '| '
181
-
182
- files.each do |file|
183
- path = destination_path file
184
- if File.exist?(path)
185
- FileUtils.rm(path)
186
- puts '|' + " x ".red + path
187
- end
188
-
189
- dir = File.dirname(path)
190
- next unless Dir[File.join(dir, '*')].empty?
191
- FileUtils.rm_r(dir)
192
- puts '|' + " x ".red + dir
193
- end
194
- puts '| '
195
-
196
- rescue
197
- UI.error "#{@msg_prefix} remove has failed" unless @config[:silent]
198
- UI.error e
199
- stop_server
200
- throw :task_has_failed
201
- end
202
- true
203
- end
204
-
205
- def jekyll_matches(paths)
206
- paths.select { |file| file =~ @extensions }
207
- end
208
-
209
- def non_jekyll_matches(paths)
210
- paths.select { |file| !file.match(/^_/) && !file.match(@extensions) }
211
- end
212
-
213
- def jekyll_config(options)
214
- if options[:config_hash]
215
- config = options[:config_hash]
216
- elsif options[:config]
217
- options[:config] = [options[:config]] unless options[:config].is_a? Array
218
- config = options
219
- end
220
- Jekyll.configuration(config)
221
- end
222
-
223
- # TODO Use Pathname.relative_path_from or similar here
224
- def local_path(path)
225
- Dir.chdir('.')
226
- current = Dir.pwd
227
- path = path.sub current, ''
228
- if path == ''
229
- './'
230
- else
231
- path.sub(/^\//, '')
232
- end
233
- end
234
-
235
- def destination_path(file)
236
- if @source =~ /^\./
237
- File.join(@destination, file)
238
- else
239
- file.sub(/^#{@source}/, "#{@destination}")
240
- end
241
- end
242
-
243
- def start_server
244
- if @server_thread.nil?
245
- @server_thread = Thread.new do
246
- Thin::Server.start(@config['host'], @config['port'], :signals => false) do
247
- require 'rack/livereload'
248
- require 'hawkins/isolation'
249
- use Rack::LiveReload,
250
- :min_delay => 500,
251
- :max_delay => 2000,
252
- :no_swf => true,
253
- :source => :vendored
254
- run ::Hawkins::IsolationInjector.new
255
- end
256
- end
257
- UI.info "#{@app_prefix} running Rack" unless @config[:silent]
258
- else
259
- UI.warning "#{@app_prefix} using an old server thread!"
260
- end
261
- end
262
-
263
- def stop_server
264
- @server_thread.kill unless @server_thread.nil?
265
- @server_thread = nil
266
- end
267
- end
268
- end