spellbook 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Gemfile +45 -0
  2. data/Gemfile.lock +65 -0
  3. data/README.mkd +63 -0
  4. data/Rakefile +35 -0
  5. data/TODO +18 -0
  6. data/VERSION +1 -0
  7. data/bin/spellbook +28 -0
  8. data/config.ru +4 -0
  9. data/db/migrate/20110419111502_create_apps.rb +13 -0
  10. data/db/migrate/20110419135238_add_proxy_to_apps.rb +9 -0
  11. data/examples/rails_hello/app/controllers/application_controller.rb +3 -0
  12. data/examples/rails_hello/app/controllers/top_controller.rb +5 -0
  13. data/examples/rails_hello/app/helpers/application_helper.rb +2 -0
  14. data/examples/rails_hello/config/application.rb +42 -0
  15. data/examples/rails_hello/config/boot.rb +6 -0
  16. data/examples/rails_hello/config/environment.rb +5 -0
  17. data/examples/rails_hello/config/environments/development.rb +26 -0
  18. data/examples/rails_hello/config/environments/production.rb +50 -0
  19. data/examples/rails_hello/config/environments/test.rb +35 -0
  20. data/examples/rails_hello/config/initializers/backtrace_silencers.rb +7 -0
  21. data/examples/rails_hello/config/initializers/inflections.rb +10 -0
  22. data/examples/rails_hello/config/initializers/mime_types.rb +5 -0
  23. data/examples/rails_hello/config/initializers/secret_token.rb +7 -0
  24. data/examples/rails_hello/config/initializers/session_store.rb +8 -0
  25. data/examples/rails_hello/config/routes.rb +60 -0
  26. data/examples/rails_hello/db/seeds.rb +7 -0
  27. data/examples/rails_hello/main.rb +76 -0
  28. data/examples/rails_hello/test/performance/browsing_test.rb +9 -0
  29. data/examples/rails_hello/test/test_helper.rb +13 -0
  30. data/examples/sinatra_hello/app.rb +124 -0
  31. data/lib/spellbook.rb +8 -0
  32. data/lib/spellbook/app.rb +16 -0
  33. data/lib/spellbook/proxy.rb +14 -0
  34. data/lib/spellbook/server.rb +202 -0
  35. data/lib/spellbook/views/apps_edit.slim +34 -0
  36. data/lib/spellbook/views/apps_index.slim +36 -0
  37. data/lib/spellbook/views/apps_new.slim +33 -0
  38. data/lib/spellbook/views/apps_show.slim +2 -0
  39. data/lib/spellbook/views/error.slim +7 -0
  40. data/lib/spellbook/views/layout.slim +22 -0
  41. data/lib/spellbook/views/screen.sass +29 -0
  42. data/lib/spellbook/views/top.slim +2 -0
  43. data/spec/database_spec.rb +29 -0
  44. data/spec/server_spec.rb +99 -0
  45. data/spec/spec_helper.rb +16 -0
  46. metadata +315 -0
@@ -0,0 +1,35 @@
1
+ RailsHello::Application.configure do
2
+ # Settings specified here will take precedence over those in config/application.rb
3
+
4
+ # The test environment is used exclusively to run your application's
5
+ # test suite. You never need to work with it otherwise. Remember that
6
+ # your test database is "scratch space" for the test suite and is wiped
7
+ # and recreated between test runs. Don't rely on the data there!
8
+ config.cache_classes = true
9
+
10
+ # Log error messages when you accidentally call methods on nil.
11
+ config.whiny_nils = true
12
+
13
+ # Show full error reports and disable caching
14
+ config.consider_all_requests_local = true
15
+ config.action_controller.perform_caching = false
16
+
17
+ # Raise exceptions instead of rendering exception templates
18
+ config.action_dispatch.show_exceptions = false
19
+
20
+ # Disable request forgery protection in test environment
21
+ config.action_controller.allow_forgery_protection = false
22
+
23
+ # Tell Action Mailer not to deliver emails to the real world.
24
+ # The :test delivery method accumulates sent emails in the
25
+ # ActionMailer::Base.deliveries array.
26
+ config.action_mailer.delivery_method = :test
27
+
28
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
29
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
30
+ # like if you have constraints or database-specific column types
31
+ # config.active_record.schema_format = :sql
32
+
33
+ # Print deprecation notices to the stderr
34
+ config.active_support.deprecation = :stderr
35
+ end
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4
+ # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5
+
6
+ # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7
+ # Rails.backtrace_cleaner.remove_silencers!
@@ -0,0 +1,10 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new inflection rules using the following format
4
+ # (all these examples are active by default):
5
+ # ActiveSupport::Inflector.inflections do |inflect|
6
+ # inflect.plural /^(ox)$/i, '\1en'
7
+ # inflect.singular /^(ox)en/i, '\1'
8
+ # inflect.irregular 'person', 'people'
9
+ # inflect.uncountable %w( fish sheep )
10
+ # end
@@ -0,0 +1,5 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
5
+ # Mime::Type.register_alias "text/html", :iphone
@@ -0,0 +1,7 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Your secret key for verifying the integrity of signed cookies.
4
+ # If you change this key, all old signed cookies will become invalid!
5
+ # Make sure the secret is at least 30 characters and all random,
6
+ # no regular words or you'll be exposed to dictionary attacks.
7
+ RailsHello::Application.config.secret_token = '306a76d5a4e533c2a46cb89f4567fbc4345bfe62165976ae9ad4f45ee7469f17c11cc8f1dc71479d6ab86829db7e68d1f123d45006031856d4ee8509c6edc36a'
@@ -0,0 +1,8 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ RailsHello::Application.config.session_store :cookie_store, :key => '_rails_hello_session'
4
+
5
+ # Use the database for sessions instead of the cookie-based default,
6
+ # which shouldn't be used to store highly confidential information
7
+ # (create the session table with "rails generate session_migration")
8
+ # RailsHello::Application.config.session_store :active_record_store
@@ -0,0 +1,60 @@
1
+ RailsHello::Application.routes.draw do
2
+ root :to => "top#hello"
3
+
4
+ # The priority is based upon order of creation:
5
+ # first created -> highest priority.
6
+
7
+ # Sample of regular route:
8
+ # match 'products/:id' => 'catalog#view'
9
+ # Keep in mind you can assign values other than :controller and :action
10
+
11
+ # Sample of named route:
12
+ # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
13
+ # This route can be invoked with purchase_url(:id => product.id)
14
+
15
+ # Sample resource route (maps HTTP verbs to controller actions automatically):
16
+ # resources :products
17
+
18
+ # Sample resource route with options:
19
+ # resources :products do
20
+ # member do
21
+ # get 'short'
22
+ # post 'toggle'
23
+ # end
24
+ #
25
+ # collection do
26
+ # get 'sold'
27
+ # end
28
+ # end
29
+
30
+ # Sample resource route with sub-resources:
31
+ # resources :products do
32
+ # resources :comments, :sales
33
+ # resource :seller
34
+ # end
35
+
36
+ # Sample resource route with more complex sub-resources
37
+ # resources :products do
38
+ # resources :comments
39
+ # resources :sales do
40
+ # get 'recent', :on => :collection
41
+ # end
42
+ # end
43
+
44
+ # Sample resource route within a namespace:
45
+ # namespace :admin do
46
+ # # Directs /admin/products/* to Admin::ProductsController
47
+ # # (app/controllers/admin/products_controller.rb)
48
+ # resources :products
49
+ # end
50
+
51
+ # You can have the root of your site routed with "root"
52
+ # just remember to delete public/index.html.
53
+ # root :to => "welcome#index"
54
+
55
+ # See how all your routes lay out with "rake routes"
56
+
57
+ # This is a legacy wild controller route that's not recommended for RESTful applications.
58
+ # Note: This route will make all actions in every controller accessible via GET requests.
59
+ # match ':controller(/:action(/:id(.:format)))'
60
+ end
@@ -0,0 +1,7 @@
1
+ # This file should contain all the record creation needed to seed the database with its default values.
2
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3
+ #
4
+ # Examples:
5
+ #
6
+ # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7
+ # Mayor.create(:name => 'Daley', :city => cities.first)
@@ -0,0 +1,76 @@
1
+ require 'tempfile'
2
+ require 'uri'
3
+
4
+ #OptionParser.new{|o|
5
+ # o.on(--url
6
+
7
+ =begin
8
+ Rails 3 'rails server':
9
+ - rails/bin/rails
10
+ - railties/lib/rails/cli.rb
11
+ - railties/lib/rails/script_rails_loader.rb
12
+ - ./script/rails
13
+ - ./config/boot.rb
14
+ require 'bundler/setup'
15
+ - railties/lib/rails/commands.rb
16
+ require 'rails/commands/server'
17
+ require APP_PATH (= config/application.rb)
18
+ - ./config/application.rb
19
+ - activerecord/lib/active_record/railtie.rb
20
+ initializer "active_record.initialize_database"
21
+ -> paths.config.database
22
+ server.start
23
+
24
+ fmm, it tries to make ./tmp ..
25
+ =end
26
+
27
+ here = File.dirname(__FILE__)
28
+
29
+ # Load rails
30
+ require File.expand_path('./config/boot', here)
31
+ require 'rails/all'
32
+
33
+ # Setup database
34
+ #data_path = TODO
35
+ #
36
+ #conf_path = Tempfile.open("spellbook"){|f|
37
+ # conf = YAML.load_file(File.expand_path("./config/database.yml",
38
+ # here))
39
+ # conf["production"]["database"] = data_path
40
+ # YAML.dump(conf, f)
41
+ #}
42
+ #
43
+ #RailsHello::Application.configure do
44
+ # paths.config.database = conf_path
45
+ #end
46
+
47
+ # Migration
48
+ =begin
49
+ - rake db:migrate
50
+ - activerecord/lib/active_record/railties/databases.rake
51
+ task :migrate => :environment do
52
+ ActiveRecord::Migration.verbose =
53
+ ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
54
+ ActiveRecord::Migrator.migrate("db/migrate/",
55
+ ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
56
+ Rake::Task["db:schema:dump"].invoke if
57
+ ActiveRecord::Base.schema_format == :ruby
58
+
59
+ Task :environment is defined here:
60
+ - railties/lib/rails/application.rb
61
+ require 'rails/tasks' #=> lib/rails/tasks/*.rake
62
+ task :environment do
63
+ $rails_rake_task = true
64
+ require_environment! #=> config/environments/production.rb?
65
+ end
66
+ =end
67
+
68
+ #system "rake db:migrate db:seed RAILS_ENV=production"
69
+
70
+ # Start rails server
71
+ ARGV.unshift "server"
72
+ #ARGV.push "--port=#{RubyStation.port}"
73
+ ARGV.push "--environment=production"
74
+
75
+ APP_PATH = File.expand_path('./config/application', here)
76
+ require 'rails/commands'
@@ -0,0 +1,9 @@
1
+ require 'test_helper'
2
+ require 'rails/performance_test_help'
3
+
4
+ # Profiling results for each test method are written to tmp/performance.
5
+ class BrowsingTest < ActionDispatch::PerformanceTest
6
+ def test_homepage
7
+ get '/'
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+ require File.expand_path('../../config/environment', __FILE__)
3
+ require 'rails/test_help'
4
+
5
+ class ActiveSupport::TestCase
6
+ # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7
+ #
8
+ # Note: You'll currently still have to declare fixtures explicitly in integration tests
9
+ # -- they do not yet inherit this setting
10
+ fixtures :all
11
+
12
+ # Add more helper methods to be used by all tests here...
13
+ end
@@ -0,0 +1,124 @@
1
+ # Load RubyGems; You need these gems installed:
2
+ # $ sudo gem install sinatra slop
3
+ require 'rubygems'
4
+
5
+ #
6
+ # Parse command-line options
7
+ #
8
+
9
+ # Slop is a handy library to parse command-line options.
10
+ require 'slop'
11
+ $opts = Slop.parse!(:help => true) do
12
+ on 'p', 'port', true, :default => 8080, :as => :integer
13
+ on 'prefix', true, :default => ""
14
+ end
15
+
16
+ #
17
+ # Sample Sinatra app
18
+ #
19
+
20
+ require 'sinatra'
21
+
22
+ # Use the HTTP port specified with --port
23
+ set :port, $opts[:port]
24
+
25
+ # Top page
26
+ get "#{$opts[:prefix]}/" do
27
+ @dice = rand(6) + 1
28
+ erb :index
29
+ end
30
+
31
+ # Help page
32
+ get "#{$opts[:prefix]}/whatsthis" do
33
+ @proxy_url = "http://localhost:3017#{$opts[:prefix]}/whatsthis"
34
+ @real_url = "http://localhost:#{$opts[:port]}#{$opts[:prefix]}/whatsthis"
35
+ erb :help
36
+ end
37
+
38
+ # Views (embedded)
39
+
40
+ __END__
41
+
42
+ @@layout
43
+ <!DOCTYPE html>
44
+ <html>
45
+ <head>
46
+ <style>
47
+ body { margin-left: 20%; margin-right: 20%; }
48
+ .box { border: 1px solid gray; padding: 1em; }
49
+ .dice { font-size: x-large; }
50
+ dt { font-weight: bold; }
51
+ </style>
52
+ </head>
53
+ <body>
54
+ <h1>
55
+ <a href="<%= $opts[:prefix] %>/">
56
+ <font color="red">D</font><font color="blue">i</font><font color="orange">c</font><font color="green">e</font>
57
+ </a>
58
+ </h1>
59
+
60
+ <%= yield %>
61
+ </body>
62
+ </html>
63
+
64
+ @@index
65
+ <p class="dice">
66
+ [
67
+ <% if @dice == 1 %>
68
+ <font color="red">1</font>
69
+ <% else %>
70
+ <%= @dice %>
71
+ <% end %>
72
+ ]
73
+ </p>
74
+ <a href="<%= $opts[:prefix] %>/">
75
+ Roll again!
76
+ </a>
77
+ <br><br><br>
78
+ <a href="<%= $opts[:prefix] %>/whatsthis">
79
+ What's this?
80
+ </a>
81
+
82
+ @@help
83
+ <h2>What's this?</h2>
84
+ <div class="box">
85
+ <p>This is a sample application for Spellbook.</p>
86
+ </div>
87
+
88
+ <h2>How to make Spellbook apps</h2>
89
+ <div class="box">
90
+ <p>
91
+ A Spellbook app is just a web application runs on localhost.
92
+ You can make Spellbook apps with your favorite programming language!
93
+ </p>
94
+ <p>
95
+ The only rule is that your app must take these options:
96
+ --port and --prefix.
97
+ </p>
98
+ <dl>
99
+ <dt>--port=XXX</dt><dd>HTTP port number.</dd>
100
+ <dt>--prefix=YYY</dt><dd>Prefix included in url.</dd>
101
+ </dl>
102
+ <p>
103
+ When Spellbook invokes an app, it passes these options like this:
104
+ </p>
105
+
106
+ <pre>$ /some/where/yourapp --port=<%= $opts[:port] %> --prefix="<%= $opts[:prefix] %>"</pre>
107
+
108
+ <p>
109
+ Then, Spellbook acts like a proxy server.<br>
110
+ This url (
111
+ <a href="<%= @proxy_url %>">
112
+ <%= @proxy_url %>
113
+ </a>
114
+ ) <br>
115
+ shows the content of
116
+ <a href="<%= @real_url %>">
117
+ <%= @real_url %>
118
+ </a>.
119
+ </p>
120
+ <p>
121
+ This sample app is written in Ruby and Sinatra.
122
+ See <%= File.expand_path __FILE__ %> for details.
123
+ </p>
124
+ </div>
data/lib/spellbook.rb ADDED
@@ -0,0 +1,8 @@
1
+ module SpellBook
2
+ VERSION = File.read(File.expand_path("../VERSION", File.dirname(__FILE__)))
3
+ end
4
+
5
+ require 'sinatra/activerecord'
6
+
7
+ require 'spellbook/proxy'
8
+ require 'spellbook/app'
@@ -0,0 +1,16 @@
1
+ module SpellBook
2
+ class App < ActiveRecord::Base
3
+ validates_presence_of :name, :port, :command
4
+ validates_uniqueness_of :name, :port
5
+ validates_numericality_of :port
6
+
7
+ def url
8
+ if self.proxy
9
+ "/#{self.name}/"
10
+ else
11
+ "http://localhost:#{self.port}/#{self.name}/"
12
+ end
13
+ end
14
+ end
15
+ #require 'irb'; IRB.start
16
+ end
@@ -0,0 +1,14 @@
1
+ require 'rack/proxy'
2
+
3
+ module SpellBook
4
+ class Proxy < Rack::Proxy
5
+ def initialize(port)
6
+ @port = port
7
+ end
8
+
9
+ def rewrite_env(env)
10
+ env["SERVER_PORT"] = @port.to_s
11
+ env
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,202 @@
1
+ require 'tempfile'
2
+ require 'sinatra/base'
3
+ require 'slim'
4
+ require 'childprocess'
5
+
6
+ module SpellBook
7
+
8
+ def self.path_to(path)
9
+ top = File.expand_path("../../", File.dirname(__FILE__))
10
+ File.expand_path(path, top)
11
+ end
12
+
13
+ class Server < Sinatra::Base
14
+ use Rack::MethodOverride
15
+
16
+ cattr_accessor :processes
17
+
18
+ configure do
19
+ set :port, SpellBook.opts[:port] || 3017
20
+ set :environment, SpellBook.opts[:environment] || "production"
21
+
22
+ set :views, SpellBook.path_to("lib/spellbook/views/")
23
+ Server.processes = {}
24
+ end
25
+
26
+ configure :test do
27
+ set :database_path, SpellBook.path_to("db/test.db")
28
+ ActiveRecord::Base.logger.level = 4
29
+ end
30
+
31
+ configure :development do
32
+ set :database_path, SpellBook.path_to("db/development.db")
33
+
34
+ require 'sinatra/reloader'
35
+ register Sinatra::Reloader
36
+ #also_reload "lib/**/*.rb"
37
+ end
38
+
39
+ configure :production do
40
+ set :database_path, File.expand_path("~/.spellbook.db")
41
+ ActiveRecord::Base.logger.level = 4
42
+ end
43
+
44
+ configure do
45
+ path = File.expand_path(SpellBook.opts[:data] ||
46
+ settings.database_path)
47
+
48
+ ActiveRecord::Base.establish_connection(
49
+ :adapter => "sqlite3",
50
+ :database => path,
51
+ )
52
+ ActiveRecord::Migrator.migrate(SpellBook.path_to('db/migrate'))
53
+
54
+ if settings.environment != "test"
55
+ puts "Spellbook version #{SpellBook::VERSION}"
56
+ puts "database: #{path}"
57
+ puts
58
+
59
+ if App.count == 0
60
+ puts "Registering sample app.."
61
+ app_path = SpellBook.path_to("examples/sinatra_hello/app.rb")
62
+ App.create!(
63
+ :name => "Sample app",
64
+ :port => 40000,
65
+ :command => "ruby #{app_path}",
66
+ :proxy => true
67
+ )
68
+ end
69
+ end
70
+ end
71
+
72
+ helpers do
73
+ def running?(app)
74
+ process = Server.processes[app.id]
75
+ process and process.alive?
76
+ end
77
+
78
+ def find_app(name)
79
+ App.find_by_name(name) or
80
+ raise "Application named `#{name}' was not found."
81
+ end
82
+ end
83
+
84
+ before do
85
+ # Remove the _method param used for PUT and DELETE
86
+ # for ActiveRecord mass-assignment
87
+ params.delete("_method")
88
+ end
89
+
90
+ # css
91
+ get '/screen.css' do
92
+ sass :screen
93
+ end
94
+
95
+ # top
96
+ get '/' do
97
+ #slim :top
98
+ redirect '/spellbook/apps'
99
+ end
100
+
101
+ # apps#index
102
+ get '/spellbook/apps/?' do
103
+ @apps = App.all
104
+ slim :apps_index
105
+ end
106
+
107
+ # apps#new
108
+ get '/spellbook/apps/new' do
109
+ begin
110
+ @port = rand(10000) + 40000
111
+ end while App.find_by_port(@port)
112
+
113
+ slim :apps_new
114
+ end
115
+
116
+ # apps#create
117
+ post '/spellbook/apps/?' do
118
+ App.new(params).save!
119
+
120
+ redirect "/spellbook/apps/"
121
+ end
122
+
123
+ # apps#edit
124
+ get '/spellbook/apps/:name/edit' do
125
+ @app = find_app(params[:name])
126
+ slim :apps_edit
127
+ end
128
+
129
+ # apps#update
130
+ put '/spellbook/apps/:name' do
131
+ app = find_app(params[:name])
132
+ app.update_attributes(params)
133
+ app.save!
134
+
135
+ redirect "/spellbook/apps/"
136
+ end
137
+
138
+ # apps#start
139
+ get '/spellbook/apps/:name/start' do
140
+ app = find_app(params[:name])
141
+
142
+ process = ChildProcess.build(*app.command.split,
143
+ "--port", app.port.to_s,
144
+ "--prefix", "/#{app.name}")
145
+ Server.processes[app.id] = process
146
+
147
+ tempfile = Tempfile.new("spellbook")
148
+ process.io.stdout = tempfile
149
+ process.io.stderr = tempfile
150
+
151
+ process.start
152
+
153
+ sleep 1
154
+
155
+ if process.exited?
156
+ tempfile.close
157
+ raise "failed to start `#{app.name}':\n\n#{File.read tempfile.path}"
158
+ end
159
+
160
+ redirect "/spellbook/apps/"
161
+ end
162
+
163
+ # apps#stop
164
+ get '/spellbook/apps/:name/stop' do
165
+ app = find_app(params[:name])
166
+
167
+ process = Server.processes[app.id]
168
+ process.stop if process
169
+
170
+ redirect "/spellbook/apps/"
171
+ end
172
+
173
+ # proxy
174
+ get '/:name/*' do
175
+ app = find_app(params[:name])
176
+
177
+ if app
178
+ n = 0
179
+ begin
180
+ SpellBook::Proxy.new(app.port).call(request.env)
181
+ rescue Errno::ECONNREFUSED => e
182
+ if n < 10
183
+ n += 1
184
+ sleep 1
185
+ retry
186
+ else
187
+ raise e
188
+ end
189
+ end
190
+ else
191
+ pass # shows sinatra's default error page
192
+ end
193
+ end
194
+
195
+ # exceptions
196
+ error do
197
+ @err = env["sinatra.error"]
198
+ slim :error
199
+ end
200
+
201
+ end
202
+ end