rack_web_console 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a4d00b4ee750af6eb2782b71b432f411964fbd4a
4
+ data.tar.gz: 60be9ea39bd4756d4b9dac911678ae8752d6f0bd
5
+ SHA512:
6
+ metadata.gz: 076756194180b419477fc145a67ac1c01e276ddca634d359d0c05de23186b1963da0c15eff378648573b16b7d85e7bc64f04133b1d8e6cb4f43a1725e2406995
7
+ data.tar.gz: ece2517ff410af142bd1a5d8447f511611b6e3fe4e8044e5a99221ae2881d28f8215d0eb5f92c83d5bdb4bd321441bccb0b9dd6316acdcff0c19396379de37cd
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ -I lib
2
+ --format documentation
3
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.12.5
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack_web_console.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rodrigo Rosenfeld Rosas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # Rack Web Console
2
+
3
+ Rack Web Console is a simple Rack app class that allows one to run arbitrary Ruby code on a given
4
+ binding, which may be useful in development mode to test some code in a given context. This is
5
+ similar to the `rails-web-console` (it was indeed extracted from it with a few enhancements) but
6
+ works for any Rack based application, including Rails.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ ```ruby
13
+ gem 'rack_web_console'
14
+ ```
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install rack_web_console
23
+
24
+ ## Usage
25
+
26
+ The hello world is not much useful, but here you are:
27
+
28
+ ```ruby
29
+ # config.ru
30
+ require 'rack_web_console'
31
+ run RackConsole.new binding
32
+ ```
33
+
34
+ Usually, you'd be more interested in learning about the binding which is usually a controller
35
+ or something like that. For example, if you want to test code from inside a Roda's `route` block:
36
+
37
+ ```ruby
38
+ # config.ru
39
+ require 'roda'
40
+ require 'rack_web_console'
41
+
42
+ class App < Roda
43
+ route do |r|
44
+ r.on('console'){ halt RackConsole.new(binding) }
45
+ 'default response'
46
+ end
47
+ end
48
+
49
+ run App
50
+ ```
51
+
52
+ The local variable `r` would be available in the console for example in this case. Some
53
+ frameworks may not have a method like Roda's `halt`, so in a Rails application for example,
54
+ you may have to do this:
55
+
56
+ ``` ruby
57
+ # app/controllers/console_controller.rb:
58
+ require 'rack_web_console'
59
+ class ConsoleController < ApplicationController
60
+ skip_before_action :verify_authenticity_token
61
+
62
+ def index
63
+ status, headers, body = RackConsole.new(binding).call(env)
64
+ response.headers.merge headers
65
+ render inline: body.join("\n"), status: status
66
+ end
67
+ end
68
+
69
+ # routes.rb:
70
+ Rails.application.routes.draw do
71
+ # ...
72
+ match 'console' => 'console#index', via: [:get, :post] if Rails.env.development?
73
+ end
74
+ ```
75
+
76
+ The `rails-web-console` gem is probably a better gem to use with Rails but this example should
77
+ demonstrate how it could be used with basically any framework. If you're not really interested
78
+ on some specific binding, you can simply mount it directly in config.ru:
79
+
80
+ ```ruby
81
+ # config.ru
82
+ require_relative 'config/environment'
83
+
84
+ require 'rack_web_console'
85
+ map('/console'){ run RackConsole.new }
86
+
87
+ run Rails.application
88
+ ```
89
+
90
+ By default, only the output of the request thread is sent to the POST request response. If you
91
+ want to spawn new threads from the script and see the output of all threads, set the
92
+ `:rack_console_capture_all` thread local to true:
93
+
94
+ ```ruby
95
+ Thread.current[:rack_console_capture_all] = true
96
+ Thread.start{ puts 'now it should be displayed in the browser' }.join
97
+ ```
98
+
99
+ ## Development
100
+
101
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec`
102
+ to run the tests. You can also run `bin/console` for an interactive prompt that will allow
103
+ you to experiment.
104
+
105
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a
106
+ new version, update the version number in `version.rb`, and then run `bundle exec rake
107
+ release`, which will create a git tag for the version, push git commits and tags, and
108
+ push the `.gem` file to [rubygems.org](https://rubygems.org).
109
+
110
+ ## Contributing
111
+
112
+ Bug reports and pull requests are welcome [on GitHub](https://github.com/rosenfeld/rack_web_console).
113
+
114
+
115
+ ## License
116
+
117
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
118
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "rack_web_console"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,60 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <title>Console</title>
5
+ <style>
6
+ #script { width: 100% }
7
+ .return { background-color: blue }
8
+ .console_result {
9
+ background-color: black;
10
+ color: white;
11
+ margin: 1em 0;
12
+ }
13
+ </style>
14
+ </head>
15
+ <body>
16
+ <textarea id="script" rows="10" cols="80"><%= script %></textarea>
17
+ <div>
18
+ <button id=clear-results>Clear (Esc, Esc)</button>
19
+ <button id=run-script>Run (Ctrl+Enter)</button>
20
+ </div>
21
+ <div id="results"></div>
22
+
23
+ <script>
24
+ var run_path = '', lastEscTime = 0,
25
+ results = $el('results'), runScriptButton = $el('run-script'),
26
+ clearResultsButton = $el('clear-results'), script = $el('script'), request
27
+ function $el(id){ return document.getElementById(id) }
28
+ addEventListener(clearResultsButton, 'click', clearResults)
29
+ addEventListener(runScriptButton, 'click', runScript)
30
+ addEventListener(script, 'keydown', onKeyDown)
31
+ function addEventListener(el, eventName, handler) {
32
+ if (el.addEventListener) el.addEventListener(eventName, handler);
33
+ else el.attachEvent('on' + eventName, handler);
34
+ }
35
+ function clearResults() {
36
+ results.innerHTML = ''
37
+ }
38
+ function runScript() {
39
+ request = new XMLHttpRequest()
40
+ request.open('POST', run_path, true)
41
+ request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8')
42
+ request.onreadystatechange = function() {
43
+ if (!(this.readyState === 4 && this.status >= 200 && this.status < 400)) return
44
+ var div = document.createElement('div')
45
+ div.setAttribute('class', 'console_result')
46
+ div.innerHTML = this.responseText
47
+ results.appendChild(div)
48
+ }
49
+ request.send('script=' + encodeURIComponent(script.value))
50
+ }
51
+ function onKeyDown(ev) {
52
+ if (ev.ctrlKey && ev.keyCode == 13) runScript() // Ctrl + Enter
53
+ if (ev.keyCode == 27) {
54
+ if (new Date().getTime() - lastEscTime < 1000) clearResults() // Esc, Esc within a second
55
+ lastEscTime = new Date().getTime()
56
+ }
57
+ }
58
+ </script>
59
+ </body>
60
+ </html>
@@ -0,0 +1,24 @@
1
+ require 'cgi'
2
+ require 'rack/utils'
3
+
4
+ class RackConsole
5
+ class CookieScriptStorage
6
+ attr_accessor :script
7
+
8
+ def initialize(env, cookie_key: '_rack-console-script', max_length: 4000)
9
+ @env, @cookie_key, @max_length = env, cookie_key, max_length
10
+ @script = ::CGI::Cookie.parse(env['HTTP_COOKIE'].to_s)[cookie_key]&.first || ''
11
+ end
12
+
13
+ WARNING_LIMIT_MSG = ->(max){ 'WARNING: stored script was limited to the first ' +
14
+ "#{max} chars to avoid issues with cookie overflow\n" }
15
+ def set_cookie_header!(headers = {})
16
+ script = @script.to_s
17
+ puts WARNING_LIMIT_MSG[@max_length] if script.size > @max_length
18
+ cookie = { value: script[0...@max_length], path: @env['REQUEST_PATH'],
19
+ domain: @env['SERVER_NAME'] }
20
+ ::Rack::Utils.set_cookie_header! headers, @cookie_key, cookie
21
+ headers
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,33 @@
1
+ require 'stringio'
2
+
3
+ class RackConsole
4
+ class OutputCapture
5
+ def initialize
6
+ @old = $stdout
7
+ @io = ::StringIO.new
8
+ @main_thread = ::Thread.current
9
+ end
10
+
11
+ def write(value)
12
+ io.write(value)
13
+ end
14
+
15
+ def capture
16
+ $stdout = self
17
+ yield
18
+ ensure
19
+ $stdout = @old
20
+ end
21
+
22
+ def output
23
+ @io.rewind
24
+ @io.read
25
+ end
26
+
27
+ private
28
+
29
+ def io
30
+ ::Thread.current == @main_thread || @main_thread[:rack_console_capture_all] ? @io : @old
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,3 @@
1
+ class RackConsole
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,52 @@
1
+ require 'erb'
2
+ require 'cgi'
3
+ require_relative 'rack_console/version'
4
+ require_relative 'rack_console/cookie_script_storage'
5
+ require_relative 'rack_console/output_capture'
6
+
7
+ class RackConsole
8
+ VIEW_TEMPLATE = ::File.join __dir__, 'rack-console-view.erb'
9
+
10
+ def initialize(_binding = binding, storage: ->(env){ CookieScriptStorage.new env })
11
+ @storage, @binding = storage, _binding
12
+ end
13
+
14
+ def call(env)
15
+ @_storage = ::Proc === @storage ? @storage[env] : @storage
16
+ env['REQUEST_METHOD'] == 'POST' ? process_script(env) : render_view(env)
17
+ end
18
+
19
+ private
20
+
21
+
22
+ def process_script(env)
23
+ script = CGI.unescape env['rack.input'].read.sub(/\Ascript=/, '')
24
+ @_storage&.script=(script)
25
+ result = []
26
+ (oc = OutputCapture.new).capture do
27
+ begin
28
+ result_eval = eval script, @binding
29
+ result << %Q{<div class="stdout">#{::ERB::Util.h oc.output}</div>}
30
+ result << %Q{<div class="return">#{::ERB::Util.h result_eval.inspect}</div>}
31
+ rescue ::Exception => e
32
+ result << e.message << "\n" << e.backtrace.join("\n")
33
+ end
34
+ end
35
+ headers = { 'Content-Type' => 'text/html; charset=utf-8' }
36
+ @_storage.set_cookie_header! headers
37
+ [ 200, headers, [ result.join("\n").gsub("\n", "<br>\n") ] ]
38
+ end
39
+
40
+ def render_view(env)
41
+ [ 200, { 'Content-Type' => 'text/html; charset=utf-8' }, [ view_response(env) ] ]
42
+ end
43
+
44
+ def view_response(env)
45
+ script = (s = @_storage&.script) ? ::ERB::Util.h(s) : ''
46
+ ::ERB.new(::File.read view_template).result binding
47
+ end
48
+
49
+ def view_template # so that it could be easily subclassed and overriden:
50
+ VIEW_TEMPLATE
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack_console/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rack_web_console'
8
+ spec.version = RackConsole::VERSION
9
+ spec.authors = ['Rodrigo Rosenfeld Rosas']
10
+ spec.email = ['rr.rosas@gmail.com']
11
+
12
+ spec.summary = %q{A web console for Rack apps.}
13
+ spec.homepage = 'https://github.com/rosenfeld/rack_web_console'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.12'
20
+ spec.add_development_dependency 'rake', '~> 10.0'
21
+ spec.add_development_dependency 'rspec', '~> 3.0'
22
+ spec.add_development_dependency 'rack'
23
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack_web_console
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Rosenfeld Rosas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.12'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.12'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rack
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - rr.rosas@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - ".rspec"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - LICENSE.txt
81
+ - README.md
82
+ - Rakefile
83
+ - bin/console
84
+ - bin/setup
85
+ - lib/rack-console-view.erb
86
+ - lib/rack_console/cookie_script_storage.rb
87
+ - lib/rack_console/output_capture.rb
88
+ - lib/rack_console/version.rb
89
+ - lib/rack_web_console.rb
90
+ - rack_web_console.gemspec
91
+ homepage: https://github.com/rosenfeld/rack_web_console
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.5.1
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: A web console for Rack apps.
115
+ test_files: []