Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

woo-hoo basic sc server works

  • Loading branch information...
commit d85fb715dbd292c3bdc7088464703eb030a3bbda 1 parent a5f83c0
Charles Jolley authored
View
6 Buildfile
@@ -65,7 +65,11 @@ mode :debug do
:combine_javascript => false,
:combine_stylesheets => false,
- :minify_javascript => false
+ :minify_javascript => false,
+
+ :build_prefix => 'tmp/debug/build',
+ :staging_prefix => 'tmp/debug/staging'
+
end
View
2  lib/sproutcore.rb
@@ -91,7 +91,7 @@ def self.logger
# compatibility reasons, :development and :debug are treated as being
# identical.
def self.build_mode
- ret = env.build_mode || :debug
+ ret = env.build_mode || :production
ret = ret.to_sym unless ret.nil?
ret = :debug if ret == :development # backwards compatibility
ret
View
69 lib/sproutcore/builders.rb
@@ -1,69 +0,0 @@
-module SproutCore
-
- # Builder classes implement the more complex algorithms for building
- # resources in SproutCore such as building HTML, JavaScript or CSS.
- # Builders are usually invoked from within build tasks which are, in-turn,
- # selected by the manifest.
- #
- class Builder
-
- # entry the current builder is working on
- attr_accessor :entry
-
- def initialize(entry=nil)
- @entry =entry
- end
-
- # override this method in subclasses to actually do build
- def build(dst_path)
- end
-
- # main entry called by build tasls
- def self.build(entry, dst_path)
- new(entry).build(dst_path)
- end
-
- # Reads the lines from the source file. If the source file does not
- # exist, returns empty array.
- def readlines(src_path)
- if File.exist?(src_path) && !File.directory?(src_path)
- File.readlines(src_path)
- else
- []
- end
- end
-
- # joins the array of lines. this is where you can also do any final
- # post-processing on the build
- def joinlines(lines)
- lines * ""
- end
-
- # writes the passed lines to the named file
- def writelines(dst_path, lines)
- FileUtils.mkdir_p(File.dirname(dst_path))
- f = File.open(dst_path, 'w')
- f.write joinlines(lines)
- f.close
- end
-
- # Handles occurances of sc_static() or static_url()
- def replace_static_url(line)
- line.gsub(/(sc_static|static_url)\(\s*['"](.+)['"]\s*\)/) do | rsrc |
- static_entry = entry.manifest.find_entry($2)
- static_url(static_entry.nil? ? '' : static_entry.url)
- end
- end
-
- # Generates the proper output for a given static url and a given target
- # this is often overridden by subclasses. the default just wraps in
- # quotes.
- def static_url(url='')
- ["'", url.gsub('"','\"'),"'"].join('')
- end
-
- end
-
-end
-
-SC.require_all_libs_relative_to(__FILE__)
View
3  lib/sproutcore/deprecated.rb
@@ -1,3 +0,0 @@
-# The deprecated directory resources we are deprecating for future use.
-
-SC.require_all_libs_relative_to(__FILE__)
View
10 lib/sproutcore/helpers.rb
@@ -1,10 +0,0 @@
-module SC
-
- # Helpers are generic utility classes and modules used by various parts of
- # the build tools.
- module Helpers
- end
-
-end
-
-SC.require_all_libs_relative_to(__FILE__)
View
1  lib/sproutcore/models.rb
@@ -1 +0,0 @@
-SC.require_all_libs_relative_to(__FILE__)
View
4 lib/sproutcore/models/project.rb
@@ -72,6 +72,10 @@ def self.load(project_root, opts={})
new project_root, opts
end
+ def reload!
+ @buildfile = @targets = nil
+ end
+
# The current buildfile for the project. The buildfile is calculated by
# merging any parent project buildfile with the contents of any
# buildfiles found in the current project. Buildfiles include any file
View
163 lib/sproutcore/rack/builder.rb
@@ -0,0 +1,163 @@
+module SC
+ module Rack
+
+ # A Rack application for serving dynamically-built SproutCore projects.
+ # Most of the time you will use this application as part of the sc-server
+ # command to dynamically build your SproutCore project while you develop
+ # it.
+ #
+ # If you are deploying some Ruby-based infrastructure in your production
+ # environment, you could also use this application to dynamically build
+ # new versions of your SproutCore apps when you deploy them. This would
+ # allow you to potentially bypass the pre-deployment build step using
+ # sc-build.
+ #
+ # While this model is supported by the Rack adaptor, it is generally
+ # recommended that you instead build you app without using this adaptor
+ # since the build step will help catch possible errors in your code before
+ # you go live with your project. Sometimes, however, dynamically building
+ # content is useful, and that is what this adaptor is for.
+ #
+ # === Using This Application
+ #
+ # When you instantiate a builder, you must provide one or more projects
+ # that contain the resources you want to load. Each incoming request url
+ # will be mapped to an entriy in a project manifest. The entry is then
+ # built and the resulting file returned. Once a file has been built, it
+ # will not be rebuilt unless the source file it represents has changed.
+ #
+ # In addition to dynamically building entries, the Builder can also
+ # forwards requests onto an SC::Rack::Proxy app to handle proxies
+ # requests.
+ #
+ # === Config Settings
+ #
+ # This app respects several options that you can name in your config file
+ # (in addition to proxy configs), that can affect the app performance.
+ # Normally reasonable defaults for these settings are built into the
+ # SproutCore buildfile, but you may choose to override them if you are
+ # deploying into a production environment.
+ #
+ # :reload_project:: If set to true, then the builder will reload the
+ # projects to look for changed files before servicing incoming
+ # requests. You will generally want this option while working in
+ # debug mode, but you may want to disable it for production, since it
+ # can slow down performance.
+ #
+ # :use_cached_headers:: If set to true, then the builder will return
+ # static assets with an "Expires: <10-years>" header attached. This
+ # will yield excellent performance in production systems but it may
+ # interfere with loading the most recent copies of files when in
+ # development mode.
+ #
+ # :combine_javascript:: If set, the generated html will reference a
+ # combined version of the javascript for elgible targets. This will
+ # yield better performance in production, but slows down load time in
+ # development mode.
+ #
+ # :combine_stylesheets:: Ditto to combine_javascript
+ #
+ class Builder
+
+ # When you create a new builder, pass in one or more projects you want
+ # the builder to monitor for changes.
+ def initialize(project)
+ @project = project
+ @last_reload_time = Time.now
+ end
+
+ # Main entry point for this Rack application. Returns 404 if no
+ # matching entry could be found in the project.
+ def call(env)
+ reload_project! # if needed
+
+ # collect some standard info
+ url = env['PATH_INFO']
+
+ # look for a matching target
+ target = target_for(url)
+ return [404, {}, "No matching target"] if target.nil?
+
+ # normalize url to resolve to entry & extract the language
+ url, language = normalize_url(url, target)
+ return [404, {}, "Target requires language"] if language.nil?
+
+ # lookup manifest
+ language = language.to_s.downcase.to_sym # normalize
+ manifest = target.manifest_for(:language => language).build!
+
+ # lookup entry by url
+ unless entry = manifest.entries.find { |e| e.url == url }
+ return [404, {}, "No matching entry in target #{target.target_name}"]
+ end
+
+ # Now build entry and return a file object
+ build_path = entry.build!.build_path
+ unless File.file?(build_path) && File.readable?(build_path)
+ return [404, {}, "File could not build"]
+ end
+
+ [200, {
+ "Last-Modified" => File.mtime(build_path).httpdate,
+ "Content-Type" => ::Rack::Mime.mime_type(File.extname(build_path), 'text/plain'),
+ "Content-Length" => File.size(build_path).to_s
+ }, File.open(build_path, 'rb')]
+ end
+
+ attr_reader :project
+
+ # Reloads the project if reloading is enabled. At maximum this will
+ # reload the project every 5 seconds.
+ def reload_project!
+ # don't reload if no project or is disabled
+ return if @project.nil? || !@project.config.reload_project
+
+ # reload at most every 5 sec
+ return if (Time.now - @last_reload_time) < 5
+
+ @last_reload_time = Time.now
+ @project.reload!
+ end
+
+ def target_for(url)
+ project.targets.values.find do |target|
+ target.prepare!
+
+ # look for a url_root match...
+ (url =~ /^#{Regexp.escape target.url_root}/) ||
+ (url =~ /^#{Regexp.escape target.index_root}/)
+ end
+ end
+
+ # Helper method. This will normalize a URL into one that can map
+ # directly to an entry in the bundle. If the URL is of a format that
+ # cannot be converted, returns the url. In particular, this will look
+ # for all the different ways you can request an index.html file and
+ # convert it to a canonical form
+ #
+ # ==== Params
+ # url<String>:: The URL
+ #
+ def normalize_url(url, target)
+
+ # Parse the URL
+ matched = url.match(/^#{Regexp.escape target.index_root}(\/([^\/\.]+))?(\/([^\/\.]+))?(\/|(\/index\.html))?$/)
+ unless matched.nil?
+ matched_language = matched[2] || target.config.preferred_language
+ matched_build_number = matched[4] || target.build_number || 'current'
+ url = [target.url_root,
+ matched_language, matched_build_number,
+ 'index.html'] * '/'
+ else
+ matched = url.match(/^#{Regexp.escape target.url_root}\/([^\/\.]+)/)
+ matched_language = matched ? matched[1] : nil
+ end
+
+ return [url, matched_language]
+ end
+
+
+ end
+
+ end
+end
View
16 lib/sproutcore/rack/docs.rb
@@ -0,0 +1,16 @@
+module SC
+ module Rack
+
+ # Rewrites relevant requests to load the SproutCore docs tools for a
+ # given project.
+ class Docs
+
+ def initialize(project)
+ @project = project
+ end
+
+ def call(env)
+ end
+ end
+ end
+end
View
15 lib/sproutcore/rack/proxy.rb
@@ -0,0 +1,15 @@
+module SC
+ module Rack
+
+ # Rack application proxies requests as needed for the given project.
+ class Proxy
+
+ def initialize(project)
+ @project = project
+ end
+
+ def call(env)
+ end
+ end
+ end
+end
View
131 lib/sproutcore/rack/service.rb
@@ -0,0 +1,131 @@
+module SC
+ module Rack
+
+ # Configures a standard set of Rack adaptors for serving SproutCore apps.
+ # Based on your config settings, this will configure appropriate
+ # middlewear apps for each incoming project. Requests are then cascaded
+ # through the projects to find one that can service the request.
+ #
+ class Service
+
+ # Convenience method to start the rack service as a server. You must
+ # pass at least a :project => foo, or :projects => [foo, bar] option
+ # to this method, but you may also pass a number of other config optios
+ # including:
+ #
+ # === Options
+ # :host:: the hostname to listen on (default 0.0.0.0)
+ # :port:: the portname to listen on (default 4020)
+ # :daemonize:: if true, service will be run as daemon
+ # :pid:: if daemonizing, this is the path of the file to write pid to
+ #
+ def self.start(opts = {})
+
+ # load rack
+ begin
+ require 'rack'
+ rescue LoadError => e
+ gem 'rack'
+ require 'rack'
+ end
+
+ # Guess.
+ if ENV.include?("PHP_FCGI_CHILDREN")
+ server = ::Rack::Handler::FastCGI
+
+ # We already speak FastCGI
+ options.delete :File
+ options.delete :Port
+ elsif ENV.include?("REQUEST_METHOD")
+ server = ::Rack::Handler::CGI
+ else
+ begin
+ server = ::Rack::Handler::Mongrel
+ rescue LoadError => e
+ server = ::Rack::Handler::WEBrick
+ end
+ end
+
+ projects = opts.delete(:projects) || [opts.delete(:project)].compact
+ app = self.new(*projects)
+
+ opts[:Host] ||= opts[:host] # allow either case.
+ opts[:Port] ||= opts[:port] || '4020'
+
+ # If daemonize is set, do it...
+ if opts[:daemonize]
+ SC.logger.info "Daemonizing..."
+ pid = opts[:pid]
+
+ if RUBY_VERSION < "1.9"
+ return if fork
+ Process.setsid
+ return if fork
+ Dir.chdir "/"
+ File.umask 0000
+ STDIN.reopen "/dev/null"
+ STDOUT.reopen "/dev/null", "a"
+ STDERR.reopen "/dev/null", "a"
+ else
+ Process.daemon
+ end
+
+ if pid
+ File.open(pid, 'w'){ |f| f.write("#{Process.pid}") }
+ at_exit { File.delete(pid) if File.exist?(pid) }
+ end
+ end
+
+ SC.logger << "Starting server at http://#{opts[:Host] || '0.0.0.0'}:#{opts[:Port]} in #{SC.build_mode} mode\n"
+ server.run app, opts
+ end
+
+ def initialize(*projects)
+ @projects = projects.flatten
+
+ # Get apps for each project & cascade if needed
+ @apps = @projects.map { |project| middleware_for(project) }
+ @app = @apps.size == 1 ? @apps.first : ::Rack::Cascade.new(@apps)
+
+ # Now put it behind some useful general optimizers...
+ @app = ::Rack::Recursive.new(@app)
+ @app = ::Rack::ConditionalGet.new(@app)
+ @app = ::Rack::Deflater.new(@app)
+
+ end
+
+ def call(env); @app.call(env); end
+
+ # Construct new middleware for the named project
+ def middleware_for(project)
+ apps = []
+
+ # setup some conditional items...
+ config = project.config
+ apps << SC::Rack::TestRunner.new(project) if config.serve_test_runner
+ apps << SC::Rack::Docs.new(project) if config.serve_docs
+ apps << SC::Rack::Proxy.new(project) if project.buildfile.proxies.size > 0
+
+ # Add builder for the project itself
+ apps << SC::Rack::Builder.new(project)
+
+ # serve files out of the public directory if serve_public is
+ # configures && the public directory exists
+ if config.serve_public
+ pubdir = File.join(project.project_root, 'public')
+ apps << ::Rack::File.new(pubdir) if File.directory?(pubdir)
+ end
+
+ # Wrap'em in a cascade if needed. This will return the first
+ # app that does not return nil
+ app = apps.size == 1 ? apps.first : ::Rack::Cascade.new(apps)
+
+ # Add show exceptions handler if enabled
+ app = ::Rack::ShowExceptions.new(app) if config.serve_exceptions
+
+ return app # done!
+ end
+
+ end
+ end
+end
View
16 lib/sproutcore/rack/test_runner.rb
@@ -0,0 +1,16 @@
+module SC
+ module Rack
+
+ # Rewrites relevant requests to load the SproutCore test runner for a
+ # given project.
+ class TestRunner
+
+ def initialize(project)
+ @project = project
+ end
+
+ def call(env)
+ end
+ end
+ end
+end
View
9 lib/sproutcore/render_engines.rb
@@ -1,9 +0,0 @@
-module SC
-
- # A RenderEngine is invoked when rendering an HTML file from a render task
- module RenderEngine
- end
-
-end
-
-SC.require_all_libs_relative_to(__FILE__)
View
4 lib/sproutcore/tools.rb
@@ -83,8 +83,8 @@ def prepare_logger!
SC.env.logfile = File.expand_path(options.logfile) if options.logfile
end
- def prepare_mode!
- build_mode = (options.mode || options.environment || 'production').to_s.downcase.to_sym
+ def prepare_mode!(preferred_mode = 'production')
+ build_mode = (options.mode || options.environment || preferred_mode).to_s.downcase.to_sym
SC.build_mode = build_mode
end
View
2  lib/sproutcore/tools/manifest.rb
@@ -21,7 +21,7 @@ def manifest(*targets)
# Copy some key props to the env
SC.env.build_prefix = options.buildroot if options.buildroot
- SC.env.stating_prefix = options.stageroot if options.stageroot
+ SC.env.staging_prefix = options.stageroot if options.stageroot
SC.env.use_symlink = options.symlink
# Verify format
View
20 lib/sproutcore/tools/server.rb
@@ -0,0 +1,20 @@
+module SC
+ class Tools
+
+ desc "server [start|stop]", "Starts the development server"
+ method_options :daemonize => false,
+ :port => :optional,
+ :host => :optional
+ def server(command='start')
+ prepare_mode!('debug') # set mode again, using debug as default
+
+ SC.env.build_prefix = options.buildroot if options.buildroot
+ SC.env.staging_prefix = options.stageroot if options.stageroot
+
+ # get project and start service.
+ project = requires_project!
+ SC::Rack::Service.start(options.merge(:project => project))
+ end
+
+ end
+end
Please sign in to comment.
Something went wrong with that request. Please try again.