heel 0.6.0 → 1.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.
@@ -0,0 +1,42 @@
1
+ #--
2
+ # Copyright (c) 2007, 2008 Jeremy Hinegardner
3
+ # All rights reserved. Licensed under the BSD license. See LICENSE for details
4
+ #++
5
+
6
+ require 'heel'
7
+ require 'rack'
8
+
9
+ module Heel
10
+
11
+ # wrapper around the rack common logger to open up the file and flush the logs
12
+ # this is invoked with a 'use' command in the Builder so a new instance of
13
+ # 'Logger' in created with each request, so we do all the heavy lifting in the
14
+ # meta class.
15
+ #
16
+ class Logger < ::Rack::CommonLogger
17
+ class << self
18
+ def log
19
+ # the log can get closed if daemonized, the at_exit will close it.
20
+ if @log.closed? then
21
+ @log = File.open(@log_file, "a")
22
+ end
23
+ @log
24
+ end
25
+
26
+ def log_file=(lf)
27
+ @log_file = lf
28
+ @log = File.open(@log_file, "a")
29
+ at_exit { @log.close unless @log.closed? }
30
+ end
31
+ end
32
+
33
+ def initialize(app)
34
+ super(app)
35
+ end
36
+
37
+ def <<(str)
38
+ Heel::Logger.log.write( str )
39
+ Heel::Logger.log.flush
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,87 @@
1
+ #--
2
+ # Copyright (c) 2007, 2008 Jeremy Hinegardner
3
+ # All rights reserved. Licensed under the BSD license. See LICENSE for details
4
+ #++
5
+ #
6
+ require 'mime/types'
7
+
8
+ module Heel
9
+
10
+ # MimeMap is a Heel specific mime mapping utility. It is based upon
11
+ # MIME::Type and adds some additional mime types. It can also say what the
12
+ # icon name for a particular mime type is.
13
+ #
14
+ class MimeMap
15
+ class << self
16
+ def icons_by_mime_type
17
+ @icons_by_mime_type ||= {
18
+ "text/plain" => "page_white_text.png",
19
+ "image" => "picture.png",
20
+ "pdf" => "page_white_acrobat.png",
21
+ "xml" => "page_white_code.png",
22
+ "compress" => "compress.png",
23
+ "gzip" => "compress.png",
24
+ "zip" => "compress.png",
25
+ "application/xhtml+xml" => "xhtml.png",
26
+ "application/word" => "page_word.png",
27
+ "application/excel" => "page_excel.png",
28
+ "application/powerpoint" => "page_white_powerpoint.png",
29
+ "text/html" => "html.png",
30
+ "application" => "application.png",
31
+ "text" => "page_white_text.png",
32
+ :directory => "folder.png",
33
+ :default => "page_white.png",
34
+ }
35
+ end
36
+
37
+ # if any other mime types are needed, add them directly via the
38
+ # mime-types calls.
39
+ def additional_mime_types
40
+ @additional_mime_types ||= [
41
+ # [ content-type , [ array, of, filename, extentions] ]
42
+ ["images/svg+xml", ["svg"]],
43
+ ["video/x-flv", ["flv"]],
44
+ ["application/x-shockwave-flash", ["swf"]],
45
+ ["text/plain", ["rb", "rhtml"]],
46
+ ]
47
+ end
48
+ end
49
+
50
+ def initialize
51
+ MimeMap.additional_mime_types.each do |mt|
52
+ if MIME::Types[mt.first].size == 0 then
53
+ type = MIME::Type.from_array(mt)
54
+ MIME::Types.add(type)
55
+ else
56
+ type = MIME::Types[mt.first].first
57
+ mt[1].each do |ext|
58
+ type.extensions << ext unless type.extensions.include?(ext)
59
+ end
60
+ # have to reindex if new extensions added
61
+ MIME::Types.index_extensions(type)
62
+ end
63
+ end
64
+ end
65
+
66
+ def default_mime_type
67
+ @default_mime_type ||= MIME::Types["application/octet-stream"].first
68
+ end
69
+
70
+ # returns the mime type of the file at a given pathname
71
+ #
72
+ def mime_type_of(f)
73
+ MIME::Types.of(f).first || default_mime_type
74
+ end
75
+
76
+ # return the icon name for a particular mime type
77
+ #
78
+ def icon_for(mime_type)
79
+ icon = nil
80
+ [:content_type, :sub_type, :media_type].each do |t|
81
+ icon = MimeMap.icons_by_mime_type[mime_type.send(t)]
82
+ return icon if icon
83
+ end
84
+ icon = MimeMap.icons_by_mime_type[:default]
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,142 @@
1
+ #--
2
+ # Copyright (c) 2007, 2008 Jeremy Hinegardner
3
+ # All rights reserved. Licensed under the BSD license. See LICENSE for details
4
+ #++
5
+
6
+ require 'heel'
7
+ require 'rack'
8
+ require 'rack/utils'
9
+ require 'coderay'
10
+ require 'coderay/helpers/file_type'
11
+
12
+ module Heel
13
+ class RackApp
14
+
15
+ attr_reader :document_root
16
+ attr_reader :directory_index_html
17
+ attr_reader :icon_url
18
+ attr_reader :highlighting
19
+ attr_reader :ignore_globs
20
+
21
+
22
+ def initialize(options = {})
23
+ @ignore_globs = options[:ignore_globs] ||= %w( *~ .htaccess . )
24
+ @document_root = options[:document_root] ||= Dir.pwd
25
+ @directory_listing_allowed = options[:directory_listing_allowed] ||= true
26
+ @directory_index_html = options[:directory_index_html] ||= "index.html"
27
+ @using_icons = options[:using_icons] ||= true
28
+ @icon_url = options[:icon_url] ||= "/heel_icons"
29
+ @highlighting = options[:highlighting] ||= false
30
+ @options = options
31
+ end
32
+
33
+ def directory_listing_allowed?
34
+ @directory_listing_allowed
35
+ end
36
+
37
+ def highlighting?
38
+ @highlighting
39
+ end
40
+
41
+ def mime_map
42
+ @mime_map ||= Heel::MimeMap.new
43
+ end
44
+
45
+ def directory_index_template_file
46
+ @directory_index_template_file ||= Heel::Configuration.data_path("listing.rhtml")
47
+ end
48
+
49
+ def directory_indexer
50
+ indexer_ignore = ( ignore_globs + [ document_root] ).flatten
51
+ @directory_indexer ||= DirectoryIndexer.new( directory_index_template_file, @options )
52
+ end
53
+
54
+
55
+ def should_ignore?(fname)
56
+ ignore_globs.each do |glob|
57
+ return true if ::File.fnmatch(glob,fname)
58
+ end
59
+ false
60
+ end
61
+
62
+ # formulate a directory index response
63
+ #
64
+ def directory_index_response(req)
65
+ response = ::Rack::Response.new
66
+ dir_index = File.join(req.request_path, directory_index_html)
67
+ if File.file?(dir_index) and File.readable?(dir_index) then
68
+ response['Content-Type'] = mime_map.mime_type_of(dir_index).to_s
69
+ response['Content-Length'] = File.size(dir_index).to_s
70
+ response.body = File.open(dir_index)
71
+ elsif directory_listing_allowed? then
72
+ body = directory_indexer.index_page_for(req)
73
+ response['Content-Type'] = 'text/html'
74
+ response['Content-Length'] = body.length.to_s
75
+ response.body << body
76
+ else
77
+ return ::Heel::ErrorResponse.new(req.path_info,"Directory index is forbidden", 403).finish
78
+ end
79
+ return response.finish
80
+ end
81
+
82
+
83
+ # formulate a file content response. Possibly a coderay highlighted file if
84
+ # it is a type that code ray can deal with and the file is not already an
85
+ # html file.
86
+ #
87
+ def file_response(req)
88
+ code_ray_type = ::FileType[req.request_path, true]
89
+ response = ::Rack::Response.new
90
+ response['Last-Modified'] = req.stat.mtime.rfc822
91
+
92
+ if highlighting? and req.highlighting? and code_ray_type and (code_ray_type != :html) then
93
+ body = <<-EOM
94
+ <html>
95
+ <head>
96
+ <title>#{req.path_info}</title>
97
+ <!-- CodeRay syntax highlighting CSS -->
98
+ <link rel="stylesheet" href="/heel_css/coderay-cycnus.css" type="text/css" />
99
+ </head>
100
+ <body>
101
+ <div class="CodeRay">
102
+ <pre>
103
+ #{CodeRay.scan_file(req.request_path,:auto).html({:line_numbers => :inline})}
104
+ </pre>
105
+ </div>
106
+ </body>
107
+ </html>
108
+ EOM
109
+ response['Content-Type'] = 'text/html'
110
+ response['Content-Length'] = body.length.to_s
111
+ response.body << body
112
+ else
113
+ file_type = mime_map.mime_type_of(req.request_path)
114
+ response['Content-Type'] = file_type.to_s
115
+ response['Content-Length'] = req.stat.size.to_s
116
+ response.body = File.open(req.request_path)
117
+ end
118
+ return response.finish
119
+ end
120
+
121
+ # interface to rack, env is a hash
122
+ #
123
+ # returns [ status, headers, body ]
124
+ #
125
+ def call(env)
126
+ req = Heel::Request.new(env, document_root)
127
+ if req.get? then
128
+ if req.forbidden? or should_ignore?(req.request_path) then
129
+ return ErrorResponse.new(req.path_info,"You do not have permissionto view #{req.path_info}",403).finish
130
+ end
131
+ return ErrorResponse.new(req.path_info, "File not found: #{req.path_info}",403).finish unless req.found?
132
+ return directory_index_response(req) if req.for_directory?
133
+ return file_response(req) if req.for_file?
134
+ else
135
+ return ErrorResponse.new(req.path_info,
136
+ "Method #{req.request_method} Not Allowed. Only GET is honored.",
137
+ 405,
138
+ { "Allow" => "GET" }).finish
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,66 @@
1
+ #--
2
+ # Copyright (c) 2007, 2008 Jeremy Hinegardner
3
+ # All rights reserved. Licensed under the BSD license. See LICENSE for details
4
+ #++
5
+ #
6
+ require 'rack'
7
+ module Heel
8
+ # nothing more than a rack request with some additional methods and overriding
9
+ # where the erros get written
10
+ class Request < ::Rack::Request
11
+
12
+ attr_reader :root_dir
13
+
14
+ # Initialize the request with the environment and the root directory of the
15
+ # request
16
+ def initialize(env, root_dir)
17
+ super(env)
18
+ @root_dir = root_dir
19
+ end
20
+
21
+ # a stat of the file mentioned in the request path
22
+ #
23
+ def stat
24
+ @stat ||= ::File.stat(request_path)
25
+ end
26
+
27
+ # normalize the request path to the full file path of the request from the
28
+ # +root_dir+
29
+ #
30
+ def request_path
31
+ @request_path ||= ::File.expand_path(::File.join(root_dir, ::Rack::Utils.unescape(path_info)))
32
+ end
33
+
34
+ #
35
+ def base_uri
36
+ @base_uri ||= ::Rack::Utils.unescape(path_info)
37
+ end
38
+
39
+
40
+ # a request must be for something that below the root directory
41
+ #
42
+ def forbidden?
43
+ request_path.index(root_dir) != 0
44
+ end
45
+
46
+ # a request is only good for something that actually exists and is readable
47
+ #
48
+ def found?
49
+ File.exist?(request_path) and (stat.directory? or stat.file?) and stat.readable?
50
+ end
51
+
52
+ def for_directory?
53
+ stat.directory?
54
+ end
55
+
56
+ def for_file?
57
+ stat.file?
58
+ end
59
+
60
+ # was the highlighting parameter true or false?
61
+ #
62
+ def highlighting?
63
+ return !(%w[ off false ].include? self.GET['highlighting'].to_s.downcase)
64
+ end
65
+ end
66
+ end
data/lib/heel/server.rb CHANGED
@@ -1,261 +1,295 @@
1
+ #--
2
+ # Copyright (c) 2007, 2008 Jeremy Hinegardner
3
+ # All rights reserved. Licensed under the BSD license. See LICENSE for details
4
+ #++
5
+
1
6
  require 'heel'
7
+ require 'thin'
2
8
  require 'ostruct'
3
9
  require 'launchy'
4
- require 'tmpdir'
5
10
  require 'fileutils'
11
+ require 'heel/rackapp'
6
12
 
7
13
  module Heel
8
- class Server
14
+ class Server
9
15
 
10
- attr_accessor :options
11
- attr_accessor :parsed_options
16
+ attr_accessor :options
17
+ attr_accessor :parsed_options
12
18
 
13
- attr_reader :stdout
14
- attr_reader :stderr
15
- attr_reader :stdin
16
-
17
-
18
- class << self
19
- # thank you Jamis - from Capistrano
20
- def home_directory # :nodoc:
21
- ENV["HOME"] ||
22
- (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
19
+ attr_reader :stdout
20
+ attr_reader :stderr
21
+ attr_reader :stdin
22
+
23
+
24
+ class << self
25
+ # thank you Jamis - from Capistrano
26
+ def home_directory # :nodoc:
27
+ ENV["HOME"] ||
28
+ (ENV["HOMEPATH"] && "#{ENV["HOMEDRIVE"]}#{ENV["HOMEPATH"]}") ||
23
29
  "/"
24
- end
25
-
26
- def kill_existing_proc
27
- Heel::Server.new.kill_existing_proc
28
- end
29
- end
30
+ end
30
31
 
31
- def initialize(argv = [])
32
- argv ||= []
32
+ def kill_existing_proc
33
+ Heel::Server.new.kill_existing_proc
34
+ end
35
+ end
33
36
 
34
- set_io
35
-
36
- @options = default_options
37
- @parsed_options = ::OpenStruct.new
38
- @parser = option_parser
39
- @error_message = nil
37
+ def initialize(argv = [])
38
+ argv ||= []
40
39
 
41
- begin
42
- @parser.parse!(argv)
43
- rescue ::OptionParser::ParseError => pe
44
- msg = ["#{@parser.program_name}: #{pe}",
40
+ set_io
41
+
42
+ @options = default_options
43
+ @parsed_options = ::OpenStruct.new
44
+ @parser = option_parser
45
+ @error_message = nil
46
+
47
+ begin
48
+ @parser.parse!(argv)
49
+ rescue ::OptionParser::ParseError => pe
50
+ msg = ["#{@parser.program_name}: #{pe}",
45
51
  "Try `#{@parser.program_name} --help` for more information"]
46
- @error_message = msg.join("\n")
47
- end
48
- end
52
+ @error_message = msg.join("\n")
53
+ end
54
+ end
49
55
 
50
- def default_options
51
- if @default_options.nil? then
52
- @default_options = ::OpenStruct.new
53
- @default_options.show_version = false
54
- @default_options.show_help = false
55
- @default_options.address = "127.0.0.1"
56
- @default_options.port = 4331
57
- @default_options.document_root = Dir.pwd
58
- @default_options.daemonize = false
59
- @default_options.highlighting = true
60
- @default_options.kill = false
61
- @default_options.launch_browser = true
62
- end
63
- return @default_options
64
- end
65
-
66
- def default_directory
67
- ENV["HEEL_DEFAULT_DIRECTORY"] || File.join(::Heel::Server.home_directory,".heel")
68
- end
69
-
70
- def pid_file
71
- File.join(default_directory,"heel.pid")
56
+ def default_options
57
+ if @default_options.nil? then
58
+ @default_options = ::OpenStruct.new
59
+ @default_options.show_version = false
60
+ @default_options.show_help = false
61
+ @default_options.address = "0.0.0.0"
62
+ @default_options.port = 4331
63
+ @default_options.document_root = Dir.pwd
64
+ @default_options.daemonize = false
65
+ @default_options.highlighting = true
66
+ @default_options.kill = false
67
+ @default_options.launch_browser = true
68
+ end
69
+ return @default_options
70
+ end
71
+
72
+ def default_directory
73
+ ENV["HEEL_DEFAULT_DIRECTORY"] || File.join(::Heel::Server.home_directory,".heel")
74
+ end
75
+
76
+ def pid_file
77
+ File.join(default_directory,"heel.pid")
78
+ end
79
+
80
+ def log_file
81
+ File.join(default_directory,"heel.log")
82
+ end
83
+
84
+ def option_parser
85
+ OptionParser.new do |op|
86
+ op.separator ""
87
+
88
+ op.on("-a", "--address ADDRESS", "Address to bind to",
89
+ " (default: #{default_options.address})") do |add|
90
+ @parsed_options.address = add
91
+ end
92
+
93
+ op.on("-d", "--daemonize", "Run daemonized in the background") do
94
+ raise ::OptionParser::ParseError, "Daemonizing is not supported on windows" if Thin.win?
95
+ @parsed_options.daemonize = true
72
96
  end
73
-
74
- def log_file
75
- File.join(default_directory,"heel.log")
97
+
98
+ op.on("-h", "--help", "Display this text") do
99
+ @parsed_options.show_help = true
76
100
  end
77
101
 
78
- def option_parser
79
- OptionParser.new do |op|
80
- op.separator ""
102
+ op.on("-k", "--kill", "Kill an existing daemonized heel process") do
103
+ @parsed_options.kill = true
104
+ end
81
105
 
82
- op.on("-a", "--address ADDRESS", "Address to bind to",
83
- " (default: #{default_options.address})") do |add|
84
- @parsed_options.address = add
85
- end
86
-
87
- op.on("-d", "--daemonize", "Run daemonized in the background") do
88
- @parsed_options.daemonize = true
89
- end
90
-
91
- op.on("-h", "--help", "Display this text") do
92
- @parsed_options.show_help = true
93
- end
94
-
95
- op.on("-k", "--kill", "Kill an existing daemonized heel process") do
96
- @parsed_options.kill = true
97
- end
98
-
99
- op.on("--[no-]highlighting", "Turn on or off syntax highlighting",
106
+ op.on("--[no-]highlighting", "Turn on or off syntax highlighting",
100
107
  " (default: on)") do |highlighting|
101
- @parsed_options.highlighting = highlighting
102
- end
103
-
104
- op.on("--[no-]launch-browser", "Turn on or off automatic browser launch",
108
+ @parsed_options.highlighting = highlighting
109
+ end
110
+
111
+ op.on("--[no-]launch-browser", "Turn on or off automatic browser launch",
105
112
  " (default: on)") do |l|
106
- @parsed_options.launch_browser = l
107
- end
113
+ @parsed_options.launch_browser = l
114
+ end
108
115
 
109
- op.on("-p", "--port PORT", Integer, "Port to bind to",
116
+ op.on("-p", "--port PORT", Integer, "Port to bind to",
110
117
  " (default: #{default_options.port})") do |port|
111
- @parsed_options.port = port
112
- end
118
+ @parsed_options.port = port
119
+ end
113
120
 
114
- op.on("-r","--root ROOT",
121
+ op.on("-r","--root ROOT",
115
122
  "Set the document root"," (default: #{default_options.document_root})") do |document_root|
116
- @parsed_options.document_root = File.expand_path(document_root)
117
- raise ::OptionParser::ParseError, "#{@parsed_options.document_root} is not a valid directory" if not File.directory?(@parsed_options.document_root)
118
- end
123
+ @parsed_options.document_root = File.expand_path(document_root)
124
+ raise ::OptionParser::ParseError, "#{@parsed_options.document_root} is not a valid directory" if not File.directory?(@parsed_options.document_root)
125
+ end
119
126
 
120
- op.on("-v", "--version", "Show version") do
121
- @parsed_options.show_version = true
122
- end
123
- end
127
+ op.on("-v", "--version", "Show version") do
128
+ @parsed_options.show_version = true
124
129
  end
130
+ end
131
+ end
125
132
 
126
- def merge_options
127
- options = default_options.marshal_dump
128
- @parsed_options.marshal_dump.each_pair do |key,value|
129
- options[key] = value
130
- end
133
+ def merge_options
134
+ options = default_options.marshal_dump
135
+ @parsed_options.marshal_dump.each_pair do |key,value|
136
+ options[key] = value
137
+ end
138
+
139
+ @options = OpenStruct.new(options)
140
+ end
141
+
142
+ # set the IO objects in a single method call. This is really only for testing
143
+ # instrumentation
144
+ def set_io(stdin = $stdin, stdout = $stdout ,setderr = $stderr)
145
+ @stdin = stdin
146
+ @stdout = stdout
147
+ @stderr = stderr
148
+ end
131
149
 
132
- @options = OpenStruct.new(options)
150
+ # if Version or Help options are set, then output the appropriate information instead of
151
+ # running the server.
152
+ def error_version_help_kill
153
+ if @parsed_options.show_version then
154
+ @stdout.puts "#{@parser.program_name}: version #{Heel::VERSION}"
155
+ exit 0
156
+ elsif @parsed_options.show_help then
157
+ @stdout.puts @parser.to_s
158
+ exit 0
159
+ elsif @error_message then
160
+ @stdout.puts @error_message
161
+ exit 1
162
+ elsif @parsed_options.kill then
163
+ kill_existing_proc
164
+ end
165
+ end
166
+
167
+ # kill an already running background heel process
168
+ def kill_existing_proc
169
+ if File.exists?(pid_file) then
170
+ begin
171
+ pid = open(pid_file).read.to_i
172
+ @stdout.puts "Sending TERM to process #{pid}"
173
+ Process.kill("TERM", pid)
174
+ rescue Errno::ESRCH
175
+ @stdout.puts "Unable to kill process with pid #{pid}. Process does not exist. Removing stale pid file."
176
+ File.unlink(pid_file)
177
+ rescue Errno::EPERM
178
+ @stdout.puts "Unable to kill process with pid #{pid}. No permissions to kill process."
133
179
  end
180
+ else
181
+ @stdout.puts "No pid file exists, no process to kill"
182
+ end
183
+ @stdout.puts "Done."
184
+ exit 0
185
+ end
134
186
 
135
- # set the IO objects in a single method call. This is really only for testing
136
- # instrumentation
137
- def set_io(stdin = $stdin, stdout = $stdout ,setderr = $stderr)
138
- @stdin = stdin
139
- @stdout = stdout
140
- @stderr = stderr
187
+ # setup the directory that heel will use as the location to run from, where its logs will
188
+ # be stored and its PID file if backgrounded.
189
+ def setup_heel_dir
190
+ if not File.exists?(default_directory) then
191
+ FileUtils.mkdir_p(default_directory)
192
+ @stdout.puts "Created #{default_directory}"
193
+ @stdout.puts "heel's PID (#{pid_file}) and log file (#{log_file}) are stored here"
194
+ end
195
+ end
196
+
197
+ # make sure that if we are daemonizing the process is not running
198
+ def ensure_not_running
199
+ if options.daemonize and File.exist?(pid_file) then
200
+ @stdout.puts "ERROR: PID File #{pid_file} already exists. Heel may already be running."
201
+ @stdout.puts "ERROR: Check the Log file #{log_file}"
202
+ @stdout.puts "ERROR: Heel will not start until the .pid file is cleared (`heel --kill' to clean it up)."
203
+ exit 1
204
+ end
205
+ end
206
+
207
+ def launch_browser
208
+ Thread.new do
209
+ print "Launching your browser"
210
+ if options.daemonize then
211
+ puts " at http://#{options.address}:#{options.port}/"
212
+ else
213
+ puts "..."
141
214
  end
215
+ ::Launchy.open("http://#{options.address}:#{options.port}/")
216
+ end
217
+ end
142
218
 
143
- # if Version or Help options are set, then output the appropriate information instead of
144
- # running the server.
145
- def error_version_help_kill
146
- if @parsed_options.show_version then
147
- @stdout.puts "#{@parser.program_name}: version #{Heel::VERSION}"
148
- exit 0
149
- elsif @parsed_options.show_help then
150
- @stdout.puts @parser.to_s
151
- exit 0
152
- elsif @error_message then
153
- @stdout.puts @error_message
154
- exit 1
155
- elsif @parsed_options.kill then
156
- kill_existing_proc
157
- end
219
+ def thin_server
220
+ server = Thin::Server.new(options.address, options.port)
221
+
222
+ # overload the name of the process so it shows up as heel not thin
223
+ def server.name
224
+ "heel (v#{Heel::VERSION})"
225
+ end
226
+
227
+ server.pid_file = pid_file
228
+ server.log_file = log_file
229
+
230
+ app = Heel::RackApp.new({ :document_root => options.document_root,
231
+ :highlighting => options.highlighting})
232
+
233
+ Heel::Logger.log_file = log_file
234
+ server.app = Rack::Builder.new {
235
+ use Heel::Logger
236
+ map "/" do
237
+ run app
158
238
  end
159
-
160
- # kill an already running background heel process
161
- def kill_existing_proc
162
- if File.exists?(pid_file) then
163
- begin
164
- pid = open(pid_file).read.to_i
165
- @stdout.puts "Sending TERM to process #{pid}"
166
- Process.kill("TERM", pid)
167
- rescue Errno::ESRCH
168
- @stdout.puts "Unable to kill process with pid #{pid}. Process does not exist. Removing stale pid file."
169
- File.unlink(pid_file)
170
- rescue Errno::EPERM
171
- @stdout.puts "Unable to kill process with pid #{pid}. No permissions to kill process."
172
- end
173
- else
174
- @stdout.puts "No pid file exists, no process to kill"
175
- end
176
- @stdout.puts "Done."
177
- exit 0
239
+ map "/heel_css" do
240
+ run Rack::File.new(Heel::Configuration.data_path( "css" ))
178
241
  end
179
-
180
- # setup the directory that heel will use as the location to run from, where its logs will
181
- # be stored and its PID file if backgrounded.
182
- def setup_heel_dir
183
- if not File.exists?(default_directory) then
184
- FileUtils.mkdir_p(default_directory)
185
- @stdout.puts "Created #{default_directory}"
186
- @stdout.puts "heel's PID (#{pid_file}) and log file (#{log_file}) are stored here"
187
- end
242
+ map "/heel_icons" do
243
+ run Rack::File.new(Heel::Configuration.data_path("famfamfam", "icons"))
188
244
  end
189
245
 
190
- # run the heel server with the current options.
191
- def run
192
-
193
- error_version_help_kill
194
- merge_options
195
- setup_heel_dir
196
-
197
- # capture method/variables into a local context so they can be used inside the Configurator block
198
- c_address = options.address
199
- c_port = options.port
200
- c_document_root = options.document_root
201
- c_background_me = options.daemonize
202
- c_default_dir = default_directory
203
- c_highlighting = options.highlighting
204
- c_pid_file = pid_file
205
- c_log_file = log_file
206
-
207
- stats = ::Mongrel::StatisticsFilter.new(:sample_rate => 1)
208
- config = ::Mongrel::Configurator.new(:host => options.address, :port => options.port, :pid_file => c_pid_file) do
209
- if c_background_me then
210
- if File.exists?(c_pid_file) then
211
- log "ERROR: PID File #{c_pid_file} already exists. Heel may already be running."
212
- log "ERROR: Check the Log file #{c_log_file}"
213
- log "ERROR: Heel will not start until the .pid file is cleared (`heel --kill' to clean it up)."
214
- exit 1
215
- end
216
- daemonize({:cwd => c_default_dir, :log_file => c_log_file})
217
- end
218
-
219
- begin
220
-
221
- listener do
222
- uri "/", :handler => stats
223
- uri "/", :handler => Heel::DirHandler.new({:document_root => c_document_root,
224
- :highlighting => c_highlighting })
225
- uri "/", :handler => Heel::ErrorHandler.new
226
- uri "/heel_css", :handler => Heel::DirHandler.new({:document_root =>
227
- File.join(APP_DATA_DIR, "css")})
228
- uri "/heel_icons", :handler => Heel::DirHandler.new({ :document_root =>
229
- File.join(APP_DATA_DIR, "famfamfam", "icons")})
230
- uri "/heel_status", :handler => ::Mongrel::StatusHandler.new(:stats_filter => stats)
231
- end
232
- rescue Errno::EADDRINUSE
233
- log "ERROR: Address (#{c_address}:#{c_port}) is already in use, please check running processes or run `heel --kill'"
234
- exit 1
235
- end
236
- setup_signals
237
- end
238
-
239
- config.run
240
- config.log "heel running at http://#{options.address}:#{options.port} with document root #{options.document_root}"
241
-
242
- if c_background_me then
243
- config.write_pid_file
246
+ }
247
+
248
+ server.app = Thin::Stats::Adapter.new(server.app, "/heel_stats")
249
+
250
+ return server
251
+ end
252
+
253
+ def start_thin_server
254
+ server = thin_server
255
+
256
+ server_thread = Thread.new do
257
+ begin
258
+ if options.daemonize then
259
+ if cpid = fork then
260
+ # wait for the top child of the server double fork to exit
261
+ Process.waitpid(cpid)
244
262
  else
245
- config.log "Use Ctrl-C to stop."
263
+ server.daemonize
264
+ server.start
246
265
  end
247
-
248
- if options.launch_browser then
249
- config.log "Launching your browser..."
250
- if c_background_me then
251
- puts "Launching your browser to http://#{options.address}:#{options.port}/"
252
- end
253
- ::Launchy.open("http://#{options.address}:#{options.port}/")
266
+ else
267
+ begin
268
+ server.start
269
+ rescue RuntimeError
270
+ $stderr.puts "ERROR: Unable to start server. Heel may already be running. Please check running processes or run `heel --kill'"
271
+ exit 1
254
272
  end
255
-
256
- config.join
257
-
273
+ end
258
274
  end
259
-
275
+ end
276
+ end
277
+
278
+
279
+ # run the heel server with the current options.
280
+ def run
281
+
282
+ error_version_help_kill
283
+ merge_options
284
+ setup_heel_dir
285
+ ensure_not_running
286
+
287
+ server_thread = start_thin_server
288
+
289
+ if options.launch_browser then
290
+ launch_browser.join
291
+ end
292
+ server_thread.join
260
293
  end
294
+ end
261
295
  end