heel 0.6.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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