require 'grounds' require 'json' require 'rack' require 'rack-slashenforce' module Rack::Grounds def self.app admin_prefix = '!' app = Grounds::App.new(Dir.pwd) Rack::Builder.app do use Rack::AppendTrailingSlash map "/#{admin_prefix}/env.json" do run Rack::Grounds.json_app { app.env } end map "/#{admin_prefix}" do run Rack::Grounds.html_app(Grounds::ADMIN_ROOT) end map '/' do run Rack::Grounds.html_app(app.public_root) end end end def self.json_app(&block) headers = { 'Content-Type' => 'application/json' } -> (env) { [200, headers, [block[env].to_json]] } end def self.html_app(root) Rack::Builder.app do use Rack::Grounds::DynamicIndex, root use Rack::Static, urls: [''], index: 'index.html', root: root run -> (env) { fail 'should never get here' } end end class DynamicIndex def initialize(app, root) @app = app @root = root end def call(env) path = File.join(@root, env['PATH_INFO']) if should_handle? path [200, { 'Content-Type' => 'text/html' }, [html_for(path)]] else @app.call(env) end end def index_for(path) File.join(path, 'index.html') end def manifest_for(path) File.join(path, 'index.manifest') end def should_handle? path %r{/$} === path and not File.exist? index_for(path) and File.exist? manifest_for(path) end def html_for(path) manifest = File.read(manifest_for(path)) open '|grounds', 'w+' do |process| parse_manifest(manifest) { |x| process.write "#{x}\n" } process.close_write process.read end end def parse_manifest(manifest) yield 'App-Attrs: manifest="index.manifest"' section = :cache for line in manifest.lines line.chomp! yield $1 if line =~ /^#= (.+)$/ line.sub! /#.*/, '' line.sub! /^\s+|\s+$/, '' case line when 'CACHE MANIFEST', '*', '' when 'CACHE:' section = :cache when 'NETWORK:' section = :network when 'FALLBACK:' section = :fallback else case section when :network yield "App-Asset: #{line}" when :cache yield "Vendor-Asset: #{line}" end end end end end end