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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.rubocop.yml +31 -6
- data/LICENSE.txt +2 -0
- data/README.md +43 -14
- data/Rakefile +13 -10
- data/hawkins.gemspec +7 -11
- data/js/WebSocketMain.swf +0 -0
- data/js/livereload.js +1055 -0
- data/js/swfobject.js +4 -0
- data/js/web_socket.js +379 -0
- data/lib/hawkins.rb +6 -40
- data/lib/hawkins/liveserve.rb +250 -0
- data/lib/hawkins/post.rb +86 -0
- data/lib/hawkins/servlet.rb +154 -0
- data/lib/hawkins/version.rb +1 -1
- data/lib/hawkins/websockets.rb +160 -0
- data/lib/mime.types +800 -0
- data/test/resources/test.crt +18 -0
- data/test/resources/test.key +28 -0
- data/test/spec_helper.rb +13 -10
- data/test/test_liveserve.rb +166 -0
- data/test/test_post.rb +171 -0
- metadata +50 -94
- data/lib/hawkins/cli.rb +0 -190
- data/lib/hawkins/guard.rb +0 -268
- data/lib/hawkins/isolation.rb +0 -116
- data/test/test_hawkins.rb +0 -134
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
|