serve 1.0.0 → 1.1.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/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
data/bin/serve CHANGED
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
+
2
3
  lib = File.dirname(__FILE__) + '/../lib'
3
- require 'rubygems'
4
4
 
5
- gem 'activesupport'
6
- require 'active_support/all'
5
+ require 'rubygems'
7
6
 
8
7
  if File.file?(lib + '/serve/version.rb')
9
8
  $LOAD_PATH << lib
10
9
  else
11
10
  gem 'serve'
12
11
  end
12
+
13
13
  require 'serve'
14
14
  require 'serve/application'
15
15
 
data/lib/serve.rb CHANGED
@@ -8,14 +8,11 @@ module Kernel
8
8
  end
9
9
 
10
10
  require 'serve/version'
11
- require 'serve/file_resolver'
11
+ require 'serve/router'
12
12
  require 'serve/handlers/file_type_handler'
13
- require 'serve/handlers/textile_handler'
14
- require 'serve/handlers/markdown_handler'
15
13
  require 'serve/handlers/dynamic_handler'
16
14
  require 'serve/handlers/sass_handler'
15
+ require 'serve/handlers/less_handler'
17
16
  require 'serve/handlers/email_handler'
18
17
  require 'serve/handlers/redirect_handler'
19
- require 'serve/handlers/static_handler'
20
- require 'serve/response_cache'
21
18
  require 'serve/rack'
@@ -78,8 +78,9 @@ module Serve
78
78
  " #{program} command [arguments] [options]",
79
79
  " ",
80
80
  "Examples:",
81
- " #{program} # start server on port 4000",
82
- " #{program} 2100 # start server on port 2100",
81
+ " #{program} # start Serve in current directory",
82
+ " #{program} 2100 # start Serve on port 2100",
83
+ " #{program} /path/to/project # start Serve for a specific directory",
83
84
  " #{program} create mockups # create a Serve project in mockups directory",
84
85
  " #{program} convert mockups # convert a Compass project in mockups",
85
86
  " ",
@@ -93,9 +94,9 @@ module Serve
93
94
  " ",
94
95
  " http://localhost:4000/",
95
96
  " ",
96
- " If the haml, redcloth, or bluecloth gems are installed the command can handle ",
97
- " Haml, Sass, SCSS, Textile, and Markdown for documents with haml, sass, scss, ",
98
- " textile, and markdown file extensions.",
97
+ " If the haml, slim, redcloth, or bluecloth gems are installed the command can ",
98
+ " handle Haml, Slim, Sass, SCSS, Textile, and Markdown for documents with haml, ",
99
+ " slim, sass, scss, textile, and markdown file extensions.",
99
100
  " ",
100
101
  " If the Rails command script/server exists in the current directory the ",
101
102
  " script will start that instead.",
@@ -216,7 +217,7 @@ module Serve
216
217
  end
217
218
 
218
219
  def run_rack_app
219
- system "rackup -p #{options[:port]} -o #{options[:address]} -E #{options[:environment]} #{rack_config}"
220
+ system "rackup -p #{options[:port]} -o #{options[:address]} -E #{options[:environment]} '#{rack_config}'"
220
221
  end
221
222
 
222
223
  def run_server
@@ -1,8 +1,19 @@
1
1
  require 'serve/view_helpers'
2
+ require 'tilt'
2
3
 
3
4
  module Serve #:nodoc:
4
5
  class DynamicHandler < FileTypeHandler #:nodoc:
5
- extension 'erb', 'html.erb', 'rhtml', 'haml', 'html.haml'
6
+
7
+ def self.extensions
8
+ # Get extensions from Tilt, ugly but it works
9
+ @extensions ||= (Tilt.mappings.map { |k,v| ["#{k}", "html.#{k}"] } << ["slim", "html.slim"]).flatten
10
+ end
11
+
12
+ def extensions
13
+ self.class.extensions
14
+ end
15
+
16
+ extension *extensions
6
17
 
7
18
  def process(request, response)
8
19
  response.headers['content-type'] = content_type
@@ -28,7 +39,8 @@ module Serve #:nodoc:
28
39
  layout = nil
29
40
  until layout or path == "/"
30
41
  path = File.dirname(path)
31
- possible_layouts = ['_layout.haml', '_layout.html.haml', '_layout.erb', '_layout.html.erb'].map do |l|
42
+ possible_layouts = extensions.map do |ext|
43
+ l = "_layout.#{ext}"
32
44
  possible_layout = File.join(root, path, l)
33
45
  File.file?(possible_layout) ? possible_layout : false
34
46
  end
@@ -44,54 +56,44 @@ module Serve #:nodoc:
44
56
  end
45
57
  end
46
58
 
47
- module ERB #:nodoc:
48
- class Engine #:nodoc:
49
- def initialize(string, options = {})
50
- @erb = ::ERB.new(string, nil, '-', '@erbout')
51
- @erb.filename = options[:filename]
52
- end
53
-
54
- def render(context, &block)
55
- # we have to keep track of the old erbout variable for nested renders
56
- # because ERB#result will set it to an empty string before it renders
57
- old_erbout = context.instance_variable_get('@erbout')
58
- result = @erb.result(context.instance_eval { binding })
59
- context.instance_variable_set('@erbout', old_erbout)
60
- result
61
- end
62
- end
63
- end
64
-
65
59
  class Parser #:nodoc:
66
- attr_accessor :context, :script_filename
60
+ attr_accessor :context, :script_filename, :script_extension, :engine
67
61
 
68
62
  def initialize(context)
69
63
  @context = context
70
64
  @context.parser = self
71
65
  end
72
66
 
73
- def parse_file(filename)
74
- old_script_filename = @script_filename
67
+ def parse_file(filename, locals={})
68
+ old_script_filename, old_script_extension, old_engine = @script_filename, @script_extension, @engine
69
+
75
70
  @script_filename = filename
76
- lines = IO.read(filename)
77
- engine = case File.extname(filename).sub(/^./, '').downcase
78
- when 'haml'
79
- require 'haml'
80
- require 'sass'
81
- require 'sass/plugin'
82
- Haml::Engine.new(lines, :attr_wrapper => '"', :filename => filename)
83
- when 'erb'
84
- require 'erb'
85
- ERB::Engine.new(lines, :filename => filename)
86
- else
87
- raise 'extension not supported'
71
+
72
+ ext = File.extname(filename).sub(/^\.html\.|^\./, '').downcase
73
+
74
+ if ext == 'slim' # Ugly, but works
75
+ if Thread.list.size > 1
76
+ warn "WARN: serve autoloading 'slim' in a non thread-safe way; " +
77
+ "explicit require 'slim' suggested."
78
+ end
79
+ require 'slim'
88
80
  end
89
- result = engine.render(context) do |*args|
81
+
82
+ @script_extension = ext
83
+
84
+ @engine = Tilt[ext].new(filename, nil, :outvar => '@_out_buf')
85
+
86
+ raise "#{ext} extension not supported" if @engine.nil?
87
+
88
+ @engine.render(context, locals) do |*args|
90
89
  context.get_content_for(*args)
91
90
  end
91
+ ensure
92
92
  @script_filename = old_script_filename
93
- result
93
+ @script_extension = old_script_extension
94
+ @engine = old_engine
94
95
  end
96
+
95
97
  end
96
98
 
97
99
  class Context #:nodoc:
@@ -103,11 +105,7 @@ module Serve #:nodoc:
103
105
  @content = ''
104
106
  end
105
107
 
106
- def _erbout
107
- @erbout
108
- end
109
-
110
108
  include Serve::ViewHelpers
111
109
  end
112
110
  end
113
- end
111
+ end
@@ -0,0 +1,19 @@
1
+ module Serve #:nodoc:
2
+
3
+ # TODO: Figure out how to remove the Less Handler in favor of Tilt
4
+ # The Less handler seems to be necessary to keep Tilt from applying a layout
5
+ # to Less files. Any one know how to turn this Tilt feature off?
6
+
7
+ class LessHandler < FileTypeHandler #:nodoc:
8
+ extension 'less'
9
+
10
+ def parse(string)
11
+ require 'less'
12
+ Less.parse(string)
13
+ end
14
+
15
+ def content_type
16
+ 'text/css'
17
+ end
18
+ end
19
+ end
@@ -1,4 +1,9 @@
1
1
  module Serve #:nodoc:
2
+
3
+ # TODO: Figure out how to remove the Sass Handler in favor of Tilt
4
+ # The Sass handler seems to be necessary to keep Tilt from applying a layout
5
+ # to Sass files. Any one know how to turn this Tilt feature off?
6
+
2
7
  class SassHandler < FileTypeHandler #:nodoc:
3
8
  extension 'sass', 'scss'
4
9
 
data/lib/serve/out.rb CHANGED
@@ -3,6 +3,13 @@ module Serve #:nodoc:
3
3
  # Utility methods for handling output to the terminal
4
4
  module Out #:nodoc:
5
5
 
6
+ COLORS = {
7
+ :clear => 0,
8
+ :red => 31,
9
+ :green => 32,
10
+ :yellow => 33
11
+ }
12
+
6
13
  def stdout
7
14
  @stdout ||= $stdout
8
15
  end
@@ -24,7 +31,7 @@ module Serve #:nodoc:
24
31
  end
25
32
 
26
33
  def print(*args)
27
- stderr.print(*args)
34
+ stderr.print(color_msg(:green, *args))
28
35
  end
29
36
 
30
37
  def log_action(name, message)
@@ -34,5 +41,16 @@ module Serve #:nodoc:
34
41
  puts message
35
42
  end
36
43
 
44
+ def color_msg(pigment, *args)
45
+ msg = ''
46
+ msg << color(pigment)
47
+ msg << "#{args.join(' ')}"
48
+ msg << color(:clear)
49
+ msg
50
+ end
51
+
52
+ def color(pigment)
53
+ "\e[#{COLORS[pigment.to_sym]}m"
54
+ end
37
55
  end
38
56
  end
data/lib/serve/project.rb CHANGED
@@ -64,7 +64,7 @@ module Serve #:nodoc:
64
64
  views
65
65
  ).each { |path| make_path path }
66
66
  create_file 'config.ru', read_template('config.ru')
67
- create_file 'LICENSE', read_template('LICENSE')
67
+ create_file 'LICENSE', read_template('license')
68
68
  create_file '.gitignore', read_template('gitignore')
69
69
  create_file 'compass.config', read_template('compass.config')
70
70
  create_file 'README.markdown', read_template('README.markdown')
@@ -173,4 +173,4 @@ module Serve #:nodoc:
173
173
  end
174
174
 
175
175
  end
176
- end
176
+ end
data/lib/serve/rack.rb CHANGED
@@ -3,20 +3,35 @@ require 'rack'
3
3
 
4
4
  module Serve
5
5
  class Request < Rack::Request
6
+ # Returns a Hash of the query params for a request's URL.
6
7
  def query
7
8
  @query ||= Rack::Utils.parse_nested_query(query_string)
8
9
  end
9
10
 
11
+ # Returns the protocol part of the URL for a request: "http://", "https://", etc.
10
12
  def protocol
11
13
  @scheme ||= scheme + "://"
12
14
  end
13
15
 
16
+ # Returns a specialized hash of the headers on the request.
14
17
  def headers
15
18
  @headers ||= Headers.new(@env)
16
19
  end
20
+
21
+ # Returns the value of the "REQUEST_URI" environment variable.
22
+ def request_uri
23
+ @env["REQUEST_URI"].to_s
24
+ end
25
+
26
+ # Set the value of the "REQUEST_URI" environment variable.
27
+ def request_uri=(s)
28
+ @env["REQUEST_URI"] = s.to_s
29
+ end
17
30
  end
18
31
 
19
32
  class Response < Rack::Response
33
+
34
+ # Set the body of a response.
20
35
  def body=(value)
21
36
  # needed for Ruby 1.9
22
37
  if value.respond_to? :each
@@ -25,9 +40,11 @@ module Serve
25
40
  super([value])
26
41
  end
27
42
  end
43
+
28
44
  end
29
45
 
30
- # Borrowed from ActionDispatch in Rails
46
+ # A specialized hash for the environment variables on a request.
47
+ # Borrowed from ActionDispatch in Rails.
31
48
  class Headers < Hash
32
49
  extend ActiveSupport::Memoizable
33
50
 
@@ -57,36 +74,59 @@ module Serve
57
74
  end
58
75
 
59
76
  class RackAdapter
77
+
78
+ # Initialize a Rack endpoint for Serve with the root path to
79
+ # the views directory.
60
80
  def initialize(root)
61
81
  @root = root
62
82
  end
63
83
 
84
+ # Called by Rack to process a request.
64
85
  def call(env)
65
86
  request = Request.new(env)
66
87
  response = Response.new()
67
88
  process(request, response).to_a
68
89
  end
69
90
 
70
- def process(request, response)
71
- path = Serve.resolve_file(@root, request.path)
72
- if path
73
- ext = File.extname(path)[1..-1]
74
- handler = Serve::FileTypeHandler.handlers[ext]
75
- if handler
76
- handler.new(@root, path).process(request, response)
77
- response
91
+ protected
92
+
93
+ # Process the request and response. Paths are transformed so that
94
+ # URLs without extensions and directory indexes work.
95
+ def process(request, response)
96
+ path = Serve::Router.resolve(@root, request.path_info)
97
+ if path
98
+ # Fetch the file handler for a file with a given extension/
99
+ ext = File.extname(path)[1..-1]
100
+ handler = Serve::FileTypeHandler.handlers[ext]
101
+ if handler
102
+ # Handler exists? Process the request and response.
103
+ handler.new(@root, path).process(request, response)
104
+ response
105
+ else
106
+ # Handler doesn't exist? Rewrite the request to use the new path.
107
+ # This allows Rack::Cascade or Passenger to deliver a file that is
108
+ # not handled by one of the Serve handlers.
109
+ rewrite(request, response, path)
110
+ end
78
111
  else
79
- default(request, response)
112
+ # Return a 404 response.
113
+ not_found(request, response)
80
114
  end
81
- else
82
- default(request, response)
83
115
  end
84
- end
116
+
117
+ # Returns a 404 response.
118
+ def not_found(request, response)
119
+ response.status = 404
120
+ response.body = "Not found!"
121
+ response
122
+ end
123
+
124
+ # Rewrite the request to use a new path. Return a 404 response so that Rack::Cascade works.
125
+ def rewrite(request, response, path)
126
+ request.request_uri = path + request.query_string
127
+ request.path_info = path
128
+ not_found(request, response)
129
+ end
85
130
 
86
- def default(request, response)
87
- response.status = 404
88
- response.body = "Not found!"
89
- response
90
- end
91
131
  end
92
132
  end
@@ -0,0 +1,43 @@
1
+ module Serve
2
+ module Router
3
+
4
+ # Resolve a path to a valid file name in root path. Return nil if no
5
+ # file exists for that path.
6
+ def self.resolve(root, path)
7
+ path = normalize_path(path)
8
+
9
+ return if path.nil? # If it's not a valid path, return nothing.
10
+
11
+ full_path = File.join(root, path)
12
+
13
+ case
14
+ when File.file?(full_path)
15
+ # A file exists! Return the matching path.
16
+ path
17
+ when File.directory?(full_path)
18
+ # It's a directory? Try a directory index.
19
+ resolve(root, File.join(path, 'index'))
20
+ when path.ends_with?('.css')
21
+ # CSS not found? Try SCSS or Sass.
22
+ alternates = %w{.scss .sass}.map { |ext| path.sub(/\.css\Z/, ext) }
23
+ sass_path = alternates.find do |p|
24
+ File.file?(File.join(root, p))
25
+ end
26
+ else
27
+ # Still no luck? Check to see if a file with an extension exists by that name.
28
+ # TODO: Return a path with an extension based on priority, not just the first found.
29
+ result = Dir.glob(full_path + ".*", File::FNM_CASEFOLD).first
30
+ result.sub(/^#{root}/, '').sub(/^\//, '') if result && File.file?(result)
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ def self.normalize_path(path)
37
+ path = File.join(path) # path may be array
38
+ path = path.sub(%r{/\Z}, '') # remove trailing slash
39
+ path unless path =~ /\.\./ # guard against evil paths
40
+ end
41
+
42
+ end
43
+ end