web-console-rails3 0.2.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.
Files changed (86) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.markdown +40 -0
  4. data/Rakefile +48 -0
  5. data/app/assets/javascripts/web_console/application.js +4 -0
  6. data/app/assets/javascripts/web_console/console_sessions.js +24 -0
  7. data/app/assets/stylesheets/web_console/application.css +13 -0
  8. data/app/assets/stylesheets/web_console/console_sessions.css +8 -0
  9. data/app/controllers/web_console/application_controller.rb +17 -0
  10. data/app/controllers/web_console/console_sessions_controller.rb +23 -0
  11. data/app/helpers/web_console/application_helper.rb +4 -0
  12. data/app/helpers/web_console/console_session_helper.rb +4 -0
  13. data/app/models/web_console/console_session.rb +110 -0
  14. data/app/views/layouts/web_console/application.html.erb +14 -0
  15. data/app/views/web_console/console_sessions/index.html.erb +4 -0
  16. data/config/routes.rb +5 -0
  17. data/lib/web-console-rails3.rb +1 -0
  18. data/lib/web-console.rb +1 -0
  19. data/lib/web_console.rb +21 -0
  20. data/lib/web_console/backport/active_model.rb +26 -0
  21. data/lib/web_console/engine.rb +40 -0
  22. data/lib/web_console/fiber.rb +48 -0
  23. data/lib/web_console/repl.rb +59 -0
  24. data/lib/web_console/repl/dummy.rb +38 -0
  25. data/lib/web_console/repl/irb.rb +62 -0
  26. data/lib/web_console/stream.rb +30 -0
  27. data/lib/web_console/version.rb +3 -0
  28. data/test/controllers/web_console/console_sessions_controller_test.rb +57 -0
  29. data/test/dummy/README.rdoc +261 -0
  30. data/test/dummy/Rakefile +7 -0
  31. data/test/dummy/app/assets/javascripts/application.js +15 -0
  32. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  33. data/test/dummy/app/controllers/application_controller.rb +3 -0
  34. data/test/dummy/app/helpers/application_helper.rb +2 -0
  35. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  36. data/test/dummy/config.ru +4 -0
  37. data/test/dummy/config/application.rb +38 -0
  38. data/test/dummy/config/boot.rb +10 -0
  39. data/test/dummy/config/database.yml +25 -0
  40. data/test/dummy/config/environment.rb +5 -0
  41. data/test/dummy/config/environments/development.rb +37 -0
  42. data/test/dummy/config/environments/production.rb +67 -0
  43. data/test/dummy/config/environments/test.rb +37 -0
  44. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  45. data/test/dummy/config/initializers/inflections.rb +15 -0
  46. data/test/dummy/config/initializers/mime_types.rb +5 -0
  47. data/test/dummy/config/initializers/secret_token.rb +7 -0
  48. data/test/dummy/config/initializers/session_store.rb +8 -0
  49. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  50. data/test/dummy/config/locales/en.yml +5 -0
  51. data/test/dummy/config/routes.rb +58 -0
  52. data/test/dummy/db/development.sqlite3 +0 -0
  53. data/test/dummy/db/schema.rb +16 -0
  54. data/test/dummy/db/test.sqlite3 +0 -0
  55. data/test/dummy/log/development.log +588 -0
  56. data/test/dummy/log/test.log +2122 -0
  57. data/test/dummy/public/404.html +26 -0
  58. data/test/dummy/public/422.html +26 -0
  59. data/test/dummy/public/500.html +25 -0
  60. data/test/dummy/public/favicon.ico +0 -0
  61. data/test/dummy/script/rails +6 -0
  62. data/test/dummy/tmp/cache/assets/C9E/AD0/sprockets%2F4a42d8916f3fa47867445f696497a92c +0 -0
  63. data/test/dummy/tmp/cache/assets/CA7/D70/sprockets%2F3e9e32bbc8c81a93002555265a36a852 +0 -0
  64. data/test/dummy/tmp/cache/assets/CBD/6E0/sprockets%2F3293dfc0984076f0e8371a9e7640f4a8 +0 -0
  65. data/test/dummy/tmp/cache/assets/CD0/FC0/sprockets%2Fbb2777627d42a216f3d9ced4322040b3 +0 -0
  66. data/test/dummy/tmp/cache/assets/CFA/E30/sprockets%2F35be1d26565dc0310c29f1a5e2f62f10 +0 -0
  67. data/test/dummy/tmp/cache/assets/D1D/FD0/sprockets%2Fa4a5ffe670666ce3d8d59179905201ef +0 -0
  68. data/test/dummy/tmp/cache/assets/D21/6B0/sprockets%2F9e242803fe56d6305274ff7e6487deda +0 -0
  69. data/test/dummy/tmp/cache/assets/D29/380/sprockets%2F92f1a0450bc4bea3364a07be42b4f645 +0 -0
  70. data/test/dummy/tmp/cache/assets/D3E/380/sprockets%2F434d98c8380bb9daf43810155aaf68ba +0 -0
  71. data/test/dummy/tmp/cache/assets/D69/670/sprockets%2Fc6b4a05846fd1b98fe9252ecdd1601c0 +0 -0
  72. data/test/dummy/tmp/cache/assets/D90/B30/sprockets%2Fe03fe93eb7fe888c9d21ee62816596ff +0 -0
  73. data/test/dummy/tmp/cache/assets/D95/C40/sprockets%2F09cb0a274209abf0391cbfce6ee67b82 +0 -0
  74. data/test/dummy/tmp/cache/assets/D9F/400/sprockets%2F7f60332f86073dc8ed80b4c2a9dfcbe1 +0 -0
  75. data/test/dummy/tmp/cache/assets/DB3/0C0/sprockets%2F689fb998e2a7add3e00db88df254c87a +0 -0
  76. data/test/dummy/tmp/cache/assets/DD4/440/sprockets%2Fa33d646ac00d8bc87a4a496af6eed96f +0 -0
  77. data/test/dummy/tmp/cache/assets/E5A/C40/sprockets%2F164cfa0fd9eb1ec1acdfaa278c83e9c5 +0 -0
  78. data/test/helpers/web_console/console_session_helper_test.rb +6 -0
  79. data/test/models/console_session_test.rb +111 -0
  80. data/test/test_helper.rb +15 -0
  81. data/test/web_console/repl/dummy_test.rb +54 -0
  82. data/test/web_console/repl/irb_test.rb +156 -0
  83. data/test/web_console/repl_test.rb +15 -0
  84. data/test/web_console_test.rb +93 -0
  85. data/vendor/assets/javascripts/jquery.console.js +727 -0
  86. metadata +249 -0
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The page you were looking for doesn't exist (404)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/404.html -->
21
+ <div class="dialog">
22
+ <h1>The page you were looking for doesn't exist.</h1>
23
+ <p>You may have mistyped the address or the page may have moved.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,26 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>The change you wanted was rejected (422)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/422.html -->
21
+ <div class="dialog">
22
+ <h1>The change you wanted was rejected.</h1>
23
+ <p>Maybe you tried to change something you didn't have access to.</p>
24
+ </div>
25
+ </body>
26
+ </html>
@@ -0,0 +1,25 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>We're sorry, but something went wrong (500)</title>
5
+ <style type="text/css">
6
+ body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
7
+ div.dialog {
8
+ width: 25em;
9
+ padding: 0 4em;
10
+ margin: 4em auto 0 auto;
11
+ border: 1px solid #ccc;
12
+ border-right-color: #999;
13
+ border-bottom-color: #999;
14
+ }
15
+ h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
16
+ </style>
17
+ </head>
18
+
19
+ <body>
20
+ <!-- This file lives in public/500.html -->
21
+ <div class="dialog">
22
+ <h1>We're sorry, but something went wrong.</h1>
23
+ </div>
24
+ </body>
25
+ </html>
File without changes
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3
+
4
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
5
+ require File.expand_path('../../config/boot', __FILE__)
6
+ require 'rails/commands'
@@ -0,0 +1,6 @@
1
+ require 'test_helper'
2
+
3
+ module WebConsole
4
+ class ConsoleHelperTest < ActionView::TestCase
5
+ end
6
+ end
@@ -0,0 +1,111 @@
1
+ require 'test_helper'
2
+
3
+ module WebConsole
4
+ class ConsoleSessionTest < ActionView::TestCase
5
+ include ActiveModel::Lint::Tests
6
+
7
+ setup do
8
+ reset_persistent_storage!
9
+ @model1 = @model = new_valid_model
10
+ @model2 = new_valid_model
11
+ end
12
+
13
+ test 'consequential ids on creation' do
14
+ assert_equal 1, @model1.id
15
+ assert_equal 2, @model2.id
16
+ end
17
+
18
+ test 'populates output on save' do
19
+ model = new_model
20
+ assert_nil model.output
21
+ model.save(input: 'puts "foo"')
22
+ assert_match %r{foo}, model.output
23
+ end
24
+
25
+ test 'populates prompt on save' do
26
+ assert_not_nil @model.prompt
27
+ end
28
+
29
+ test 'preserved models can be found' do
30
+ id = @model.tap(&:save).id
31
+ assert_equal @model, ConsoleSession.find(id)
32
+ end
33
+
34
+ test 'trying to find a model fails if no longer in storage' do
35
+ assert_raises(ConsoleSession::NotFound) { ConsoleSession.find(0) }
36
+ end
37
+
38
+ test 'find coerces ids' do
39
+ id = @model.tap(&:save).id
40
+ assert_equal @model, ConsoleSession.find("#{id}")
41
+ end
42
+
43
+ test 'not found exceptions are json serializable' do
44
+ exception = assert_raises(ConsoleSession::NotFound) do
45
+ ConsoleSession.find(0)
46
+ end
47
+ assert_equal '{"error":"Session unavailable"}', exception.to_json
48
+ end
49
+
50
+ test 'persisted models knows that they are in memory' do
51
+ refute @model.persisted?
52
+ @model.save
53
+ assert @model.persisted?
54
+ end
55
+
56
+ test 'persisted models knows about their keys' do
57
+ assert_nil @model.to_key
58
+ @model.save
59
+ assert_equal [1], @model.to_key
60
+ end
61
+
62
+ test 'supports json serialization' do
63
+ rails3 = Rails::VERSION::MAJOR == 3
64
+
65
+ with_dummy_adapter do
66
+ model = new_model
67
+ expected_nil_json = "{\"id\":3,\"input\":null,\"output\":null,\"prompt\":\"#{rails3 ? ">>" : "\\u003E\\u003E"} \"}"
68
+ assert_equal expected_nil_json, model.to_json
69
+
70
+ model.save(input: 'puts "foo"')
71
+ expected_json = "{\"id\":3,\"input\":\"puts \\\"foo\\\"\",\"output\":\"foo\\n=#{rails3 ? ">" : "\\u003E"} nil\\n\",\"prompt\":\"#{rails3 ? ">>" : "\\u003E\\u003E"} \"}"
72
+ assert_equal expected_json, model.to_json
73
+ end
74
+ end
75
+
76
+ test 'create gives already persisted models' do
77
+ assert ConsoleSession.create.persisted?
78
+ end
79
+
80
+ test 'no gives not persisted models' do
81
+ refute ConsoleSession.new.persisted?
82
+ end
83
+
84
+ private
85
+ def new_model(attributes = {})
86
+ ConsoleSession.new(attributes)
87
+ end
88
+
89
+ def new_valid_model(attributes = {})
90
+ attributes.merge!(input: 'puts "foo"') unless attributes[:input].present?
91
+ new_model(attributes)
92
+ end
93
+
94
+ def reset_persistent_storage!
95
+ ConsoleSession::INMEMORY_STORAGE.clear
96
+ ConsoleSession.class_variable_set(:@@counter, 0)
97
+ end
98
+
99
+ def with_dummy_adapter
100
+ previous_method = WebConsole::REPL.method(:default)
101
+ WebConsole::REPL.module_eval do
102
+ define_singleton_method(:default) { WebConsole::REPL::Dummy }
103
+ end
104
+ yield
105
+ ensure
106
+ WebConsole::REPL.module_eval do
107
+ define_singleton_method(:default, &previous_method)
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,15 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require File.expand_path("../dummy/config/environment.rb", __FILE__)
5
+ require "rails/test_help"
6
+
7
+ Rails.backtrace_cleaner.remove_silencers!
8
+
9
+ # Load support files
10
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
11
+
12
+ # Load fixtures from the engine
13
+ if ActiveSupport::TestCase.method_defined?(:fixture_path=)
14
+ ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__)
15
+ end
@@ -0,0 +1,54 @@
1
+ require 'test_helper'
2
+
3
+ class REPLTest < ActiveSupport::TestCase
4
+ setup do
5
+ @dummy1 = @dummy = WebConsole::REPL::Dummy.new
6
+ @dummy2 = WebConsole::REPL::Dummy.new
7
+ end
8
+
9
+ test 'sending input returns the result as output' do
10
+ assert_equal "=> 42\n", @dummy.send_input('foo = 42')
11
+ end
12
+
13
+ test 'preserves the session in the binding' do
14
+ assert_equal "=> 42\n", @dummy.send_input('foo = 42')
15
+ assert_equal "=> 50\n", @dummy.send_input('foo + 8')
16
+ end
17
+
18
+ test 'session isolation requires own bindings' do
19
+ dummy1 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
20
+ dummy2 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
21
+ assert_equal "=> 42\n", dummy1.send_input('foo = 42')
22
+ assert_match %r{NameError}, dummy2.send_input('foo')
23
+ end
24
+
25
+ test 'session preservation requires same bindings' do
26
+ assert_equal "=> 42\n", @dummy1.send_input('foo = 42')
27
+ assert_equal "=> 42\n", @dummy2.send_input('foo')
28
+ end
29
+
30
+ test 'captures stdout output' do
31
+ assert_equal "42\n=> nil\n", @dummy.send_input('puts 42')
32
+ end
33
+
34
+ test 'captures stderr output' do
35
+ assert_equal "42\n=> 3\n", @dummy.send_input('$stderr.write("42\n")')
36
+ end
37
+
38
+ test 'prompt is present' do
39
+ assert_not_nil @dummy.prompt
40
+ end
41
+
42
+ test 'rails helpers are available in the session' do
43
+ each_rails_console_method do |meth|
44
+ assert_equal "=> true\n", @dummy.send_input("respond_to? :#{meth}")
45
+ end
46
+ end
47
+
48
+ private
49
+ def each_rails_console_method(&block)
50
+ require 'rails/console/app'
51
+ require 'rails/console/helpers'
52
+ Rails::ConsoleMethods.public_instance_methods.each(&block)
53
+ end
54
+ end
@@ -0,0 +1,156 @@
1
+ require 'test_helper'
2
+
3
+ class IRBTest < ActiveSupport::TestCase
4
+ setup do
5
+ @irb1 = @irb = WebConsole::REPL::IRB.new
6
+ @irb2 = WebConsole::REPL::IRB.new
7
+
8
+ # Flag to signalize that the .irbrc was read.
9
+ $IRBRC_EXECUTED = false
10
+
11
+ # Since IRB is kinda funky, it reads the .irbrc in $HOME/.irbrc earlier
12
+ # that the one in the current working directory, we have to lie to it.
13
+ @preserved_home, ENV['HOME'] = ENV['HOME'], nil
14
+
15
+ # It also caches the procedure used to generate the .irbrc location.
16
+ IRB.conf[:RC_NAME_GENERATOR] = nil
17
+ end
18
+
19
+ teardown do
20
+ # Now, bring the working place as we have found it.
21
+ ENV['HOME'] = @preserved_home
22
+ WebConsole::Engine.config.web_console.prevent_irbrc_execution = false
23
+ end
24
+
25
+ test 'sending input returns the result as output' do
26
+ assert_equal return_prompt(42), @irb.send_input('foo = 42')
27
+ end
28
+
29
+ test 'preserves the session in the binding' do
30
+ assert_equal return_prompt(42), @irb.send_input('foo = 42')
31
+ assert_equal return_prompt(50), @irb.send_input('foo + 8')
32
+ end
33
+
34
+ test 'session isolation requires own bindings' do
35
+ irb1 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
36
+ irb2 = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
37
+ assert_equal return_prompt(42), irb1.send_input('foo = 42')
38
+ assert_match %r{NameError}, irb2.send_input('foo')
39
+ end
40
+
41
+ test 'session preservation requires same bindings' do
42
+ assert_equal return_prompt(42), @irb1.send_input('foo = 42')
43
+ assert_equal return_prompt(42), @irb2.send_input('foo')
44
+ end
45
+
46
+ test 'multiline sessions' do
47
+ irb = WebConsole::REPL::IRB.new(Object.new.instance_eval('binding'))
48
+ assert_equal "", irb.send_input('class A')
49
+ assert_equal return_prompt('nil'), irb.send_input('end')
50
+ assert_no_match %r{NameError}, irb.send_input('A')
51
+ end
52
+
53
+ test 'captures direct stdout output' do
54
+ assert_equal "42\n#{return_prompt('nil')}", @irb.send_input('puts 42')
55
+ end
56
+
57
+ test 'captures direct stderr output' do
58
+ assert_equal "42\n#{return_prompt(3)}", @irb.send_input('$stderr.write("42\n")')
59
+ end
60
+
61
+ test 'captures direct output from subprocesses' do
62
+ assert_equal "42\n#{return_prompt(true)}", @irb.send_input('system "echo 42"')
63
+ end
64
+
65
+ test 'captures direct output from forks' do
66
+ # This is a bummer, but currently I don't see how we can work around it,
67
+ # without monkey patching fork and the crew to be blocking calls. This
68
+ # won't scale well, but at least fork will show results. Otherwise, we can
69
+ # document the behaviour and expect the user to wait themselves, if they
70
+ # care about the output.
71
+ assert_match %r{42\n}, @irb.send_input('Process.wait(fork { puts 42 })')
72
+ end
73
+
74
+ test 'multiline support between threads' do
75
+ assert_equal "", @irb.send_input('class A')
76
+ Thread.new do
77
+ assert_equal return_prompt('nil'), @irb.send_input('end')
78
+ assert_no_match %r{NameError}, @irb.send_input('A')
79
+ end.join
80
+ end
81
+
82
+ test 'prompt is present' do
83
+ assert_not_nil @irb.prompt
84
+ end
85
+
86
+ test 'prompt is determined by ::IRB.conf' do
87
+ with_simple_prompt do
88
+ assert '>> ', WebConsole::REPL::IRB.new.prompt
89
+ end
90
+ end
91
+
92
+ test 'closest .irbrc is executed by default' do
93
+ skip "I'm special. Travis is special. Who knows?"
94
+ Dir.chdir(File.expand_path('../../../fixtures', __FILE__)) do
95
+ WebConsole::REPL::IRB.new.tap { assert $IRBRC_EXECUTED }
96
+ end
97
+ end
98
+
99
+ test 'closest .irbrc execution can be prevented' do
100
+ WebConsole::Engine.config.web_console.prevent_irbrc_execution = true
101
+ Dir.chdir(File.expand_path('../../../fixtures', __FILE__)) do
102
+ WebConsole::REPL::IRB.new.tap { refute $IRBRC_EXECUTED }
103
+ end
104
+ end
105
+
106
+ test 'rails helpers are available in the session' do
107
+ each_rails_console_method do |meth|
108
+ assert_equal return_prompt(true), @irb.send_input("respond_to? :#{meth}")
109
+ end
110
+ end
111
+
112
+ private
113
+ def currently_selected_prompt
114
+ ::IRB.conf[:PROMPT][::IRB.conf[:PROMPT_MODE]]
115
+ end
116
+
117
+ def return_prompt(*args)
118
+ sprintf(currently_selected_prompt[:RETURN], *args)
119
+ end
120
+
121
+ def input_prompt
122
+ currently_selected_prompt[:PROMPT_I]
123
+ end
124
+
125
+ def with_simple_prompt
126
+ previous_prompt = ::IRB.conf[:PROMPT]
127
+ ::IRB.conf[:PROMPT] = :simple
128
+ yield
129
+ ensure
130
+ ::IRB.conf[:PROMPT] = previous_prompt
131
+ end
132
+
133
+ def each_rails_console_method(&block)
134
+ require 'rails/console/app'
135
+ require 'rails/console/helpers'
136
+ Rails::ConsoleMethods.public_instance_methods.each(&block)
137
+ end
138
+
139
+ def new_uninitialized_app(root = File.expand_path('../../../dummy', __FILE__))
140
+ FileUtils.mkdir_p root
141
+ Dir.chdir root
142
+
143
+ old_app = Rails.application
144
+ Rails.application = nil
145
+
146
+ app = Class.new(Rails::Application)
147
+ app.config.eager_load = false
148
+ app.config.time_zone = 'UTC'
149
+ app.config.middleware ||= Rails::Configuration::MiddlewareStackProxy.new
150
+ app.config.active_support.deprecation = :notify
151
+
152
+ yield app
153
+ ensure
154
+ Rails.application = old_app
155
+ end
156
+ end