mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-09 15:08:04 -05:00
Remove Ruby implementation... *sniff*
This commit is contained in:
5
Gemfile
5
Gemfile
@@ -1,5 +0,0 @@
|
||||
source 'https://rubygems.org'
|
||||
|
||||
gem 'em-websocket'
|
||||
gem 'eventmachine_httpserver'
|
||||
gem 'json'
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -1,18 +0,0 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.2.8)
|
||||
em-websocket (0.3.6)
|
||||
addressable (>= 2.1.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
eventmachine (0.12.10)
|
||||
eventmachine_httpserver (0.2.1)
|
||||
json (1.7.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
em-websocket
|
||||
eventmachine_httpserver
|
||||
json
|
||||
19
hash.rb
19
hash.rb
@@ -1,19 +0,0 @@
|
||||
require 'shiny'
|
||||
require 'digest/sha1'
|
||||
require 'digest/md5'
|
||||
|
||||
shinyapp = ShinyApp.new
|
||||
|
||||
input1 = React::ObservableValue.new {
|
||||
shinyapp.session.get('input1') + (shinyapp.session.get('addnewline') ? "\n" : '')
|
||||
}
|
||||
|
||||
shinyapp.define_output('md5_hash') do
|
||||
Digest::MD5.hexdigest(input1.value)
|
||||
end
|
||||
|
||||
shinyapp.define_output('sha1_hash') do
|
||||
Digest::SHA1.hexdigest(input1.value)
|
||||
end
|
||||
|
||||
shinyapp.run
|
||||
172
lib/react.rb
172
lib/react.rb
@@ -1,172 +0,0 @@
|
||||
module React
|
||||
|
||||
class Context
|
||||
|
||||
private
|
||||
|
||||
@@next_id = 0
|
||||
@@current_context = nil
|
||||
@@pending_invalidate = []
|
||||
|
||||
public
|
||||
|
||||
def self.current!
|
||||
return current || raise("No current context")
|
||||
end
|
||||
|
||||
def self.current
|
||||
@@current_context
|
||||
end
|
||||
|
||||
attr_reader :id
|
||||
|
||||
def initialize
|
||||
# The ID can used to identify/sort/dedupe contexts
|
||||
@id = @@next_id += 1
|
||||
|
||||
# Indicates whether this context is invalidated, i.e. its
|
||||
# callbacks have been called or are about to be called
|
||||
@invalidated = false
|
||||
|
||||
# List of callbacks to be called after invalidation
|
||||
@callbacks = []
|
||||
end
|
||||
|
||||
# Run a block with this context as the current context. The
|
||||
# original current context will be restored after the block
|
||||
# is executed.
|
||||
def run
|
||||
old_ctx = @@current_context
|
||||
@@current_context = self
|
||||
begin
|
||||
return yield
|
||||
ensure
|
||||
@@current_context = old_ctx
|
||||
end
|
||||
end
|
||||
|
||||
def invalidate
|
||||
return if @invalidated
|
||||
|
||||
@invalidated = true
|
||||
@@pending_invalidate << self
|
||||
end
|
||||
|
||||
# Register a callback to be called after this context is
|
||||
# invalidated (or immediately if it's already invalidated).
|
||||
# The callback takes one argument, the context.
|
||||
def on_invalidate(&callback)
|
||||
if @invalidated
|
||||
callback.call(self)
|
||||
else
|
||||
@callbacks << callback
|
||||
end
|
||||
end
|
||||
|
||||
def execute_callbacks
|
||||
@callbacks.each {|callback| callback.call(self)}
|
||||
end
|
||||
|
||||
# Execute all callbacks on invalidated contexts. Will do this
|
||||
# repeatedly if the callbacks themselves cause more invalidations.
|
||||
def self.flush
|
||||
while !@@pending_invalidate.empty?
|
||||
contexts = @@pending_invalidate
|
||||
@@pending_invalidate = []
|
||||
|
||||
contexts.each {|context| context.execute_callbacks}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Session
|
||||
def initialize
|
||||
# Key is variable name, value is variable value
|
||||
@values = Hash.new
|
||||
# Key is variable name, value is { Context IDs => Contexts }
|
||||
@dependencies = Hash.new
|
||||
end
|
||||
|
||||
def get(name)
|
||||
cur_ctx = React::Context.current!
|
||||
@dependencies[name] = @dependencies[name] || Hash.new
|
||||
if !@dependencies[name].has_key?(cur_ctx.id)
|
||||
@dependencies[name][cur_ctx.id] = cur_ctx
|
||||
cur_ctx.on_invalidate do
|
||||
@dependencies[name].delete(cur_ctx.id)
|
||||
end
|
||||
end
|
||||
|
||||
return @values[name]
|
||||
end
|
||||
|
||||
def set(name, value)
|
||||
if @values.has_key?(name) && @values[name] == value
|
||||
return
|
||||
end
|
||||
|
||||
@values[name] = value
|
||||
if @dependencies[name]
|
||||
@dependencies[name].each_value {|ctx| ctx.invalidate}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Stores (and caches) a single dependent value in a context
|
||||
class ObservableValue
|
||||
def initialize(&valueProc)
|
||||
@valueProc = valueProc
|
||||
|
||||
@dependencies = Hash.new
|
||||
@initialized = false
|
||||
end
|
||||
|
||||
def value
|
||||
if !@initialized
|
||||
@initialized = true
|
||||
update_value
|
||||
end
|
||||
|
||||
cur_ctx = React::Context.current!
|
||||
@dependencies[cur_ctx.id] = cur_ctx
|
||||
cur_ctx.on_invalidate do
|
||||
@dependencies.delete cur_ctx.id
|
||||
end
|
||||
@value
|
||||
end
|
||||
|
||||
private
|
||||
def update_value
|
||||
old_value = @value
|
||||
|
||||
ctx = Context.new
|
||||
ctx.on_invalidate do
|
||||
update_value
|
||||
end
|
||||
ctx.run do
|
||||
@value = @valueProc.call
|
||||
end
|
||||
|
||||
if old_value != @value
|
||||
@dependencies.each_value {|dep_ctx| dep_ctx.invalidate}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Runs the given proc whenever its dependencies change
|
||||
class Observer
|
||||
def initialize(&proc)
|
||||
@proc = proc
|
||||
run
|
||||
end
|
||||
|
||||
def run
|
||||
ctx = React::Context.new
|
||||
ctx.on_invalidate do
|
||||
run
|
||||
end
|
||||
ctx.run &@proc
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
156
lib/shiny.rb
156
lib/shiny.rb
@@ -1,156 +0,0 @@
|
||||
require 'eventmachine'
|
||||
require 'evma_httpserver'
|
||||
require 'em-websocket'
|
||||
require 'pathname'
|
||||
require 'json'
|
||||
require 'react'
|
||||
|
||||
|
||||
class WebServer < EM::Connection
|
||||
include EM::HttpServer
|
||||
|
||||
def post_init
|
||||
super
|
||||
no_environment_strings
|
||||
|
||||
@basepath = File.join(Dir.pwd, 'www')
|
||||
end
|
||||
|
||||
def resolve_path(path)
|
||||
# It's not a valid path if it doesn't start with /
|
||||
return nil if path !~ /^\//
|
||||
|
||||
abspath = File.join(@basepath, "./#{path}")
|
||||
# Resolves '..', etc.
|
||||
abspath = Pathname.new(abspath).cleanpath.to_s
|
||||
|
||||
return false if abspath[0...(@basepath.size + 1)] != @basepath + '/'
|
||||
return false if !File.exist?(abspath)
|
||||
|
||||
return abspath
|
||||
end
|
||||
|
||||
def process_http_request
|
||||
# the http request details are available via the following instance variables:
|
||||
# @http_protocol
|
||||
# @http_request_method
|
||||
# @http_cookie
|
||||
# @http_if_none_match
|
||||
# @http_content_type
|
||||
# @http_path_info
|
||||
# @http_request_uri
|
||||
# @http_query_string
|
||||
# @http_post_content
|
||||
# @http_headers
|
||||
|
||||
response = EM::DelegatedHttpResponse.new(self)
|
||||
|
||||
path = @http_path_info
|
||||
path = '/index.html' if path == '/'
|
||||
|
||||
resolved_path = resolve_path(path)
|
||||
|
||||
if !resolved_path
|
||||
response.status = 404
|
||||
response.content_type 'text/html'
|
||||
response.content = '<h1>404 Not Found</h1>'
|
||||
else
|
||||
response.status = 200
|
||||
response.content_type case resolved_path
|
||||
when /\.html?$/
|
||||
'text/html'
|
||||
when /\.js$/
|
||||
'text/javascript'
|
||||
when /\.css$/
|
||||
'text/css'
|
||||
when /\.png$/
|
||||
'image/png'
|
||||
when /\.jpg$/
|
||||
'image/jpeg'
|
||||
when /\.gif$/
|
||||
'image/gif'
|
||||
end
|
||||
response.content = File.read(resolved_path)
|
||||
end
|
||||
response.send_response
|
||||
end
|
||||
end
|
||||
|
||||
def run_shiny_app(shinyapp)
|
||||
EventMachine.run do
|
||||
EventMachine.start_server '0.0.0.0', 8100, WebServer
|
||||
puts "Listening on port 8100"
|
||||
|
||||
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8101) do |ws|
|
||||
shinyapp.websocket = ws
|
||||
ws.onclose { exit(0) }
|
||||
ws.onmessage do |msg|
|
||||
begin
|
||||
puts "RECV: #{msg}"
|
||||
|
||||
msg_obj = JSON.parse(msg)
|
||||
case msg_obj['method']
|
||||
when 'init'
|
||||
msg_obj['data'].each do |k, v|
|
||||
shinyapp.session.set(k, v)
|
||||
end
|
||||
React::Context.flush
|
||||
shinyapp.instantiate_outputs
|
||||
when 'update'
|
||||
msg_obj['data'].each do |k, v|
|
||||
shinyapp.session.set(k, v)
|
||||
end
|
||||
end
|
||||
|
||||
React::Context.flush
|
||||
shinyapp.flush_output
|
||||
rescue Exception => e
|
||||
puts "ERROR: #{e}"
|
||||
puts e.backtrace.collect {|x| "\t#{x}"}
|
||||
raise
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ShinyApp
|
||||
attr_reader :session
|
||||
|
||||
def initialize
|
||||
@session = React::Session.new
|
||||
@outputs = {}
|
||||
@invalidated_output_values = Hash.new
|
||||
end
|
||||
|
||||
def websocket=(value)
|
||||
@websocket = value
|
||||
end
|
||||
|
||||
def define_output(name, &proc)
|
||||
@outputs[name] = proc
|
||||
end
|
||||
|
||||
def instantiate_outputs
|
||||
@outputs.keys.each do |name|
|
||||
proc = @outputs.delete(name)
|
||||
React::Observer.new do
|
||||
value = proc.call
|
||||
@invalidated_output_values[name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def flush_output
|
||||
return if @invalidated_output_values.empty?
|
||||
|
||||
data = @invalidated_output_values
|
||||
@invalidated_output_values = Hash.new
|
||||
puts "SEND: #{JSON.generate(data)}"
|
||||
@websocket.send(JSON.generate(data))
|
||||
end
|
||||
|
||||
def run
|
||||
run_shiny_app self
|
||||
end
|
||||
end
|
||||
@@ -1,23 +0,0 @@
|
||||
require 'react'
|
||||
|
||||
include React
|
||||
|
||||
def print_observable_value(obsVal)
|
||||
Observer.new { puts obsVal.value }
|
||||
end
|
||||
|
||||
sess = Session.new
|
||||
sess.set('user', '')
|
||||
|
||||
user = ObservableValue.new { sess.get('user') }
|
||||
user_caps = ObservableValue.new { user.value.upcase }
|
||||
|
||||
# This will print the value not just once, but every
|
||||
# time the value changes
|
||||
print_observable_value(user_caps)
|
||||
|
||||
sess.set('user', 'jcheng')
|
||||
Context.flush # pay no attention to the man behind the curtain
|
||||
sess.set('user', 'jjallaire')
|
||||
sess.set('user', 'jwpaulson')
|
||||
Context.flush
|
||||
Reference in New Issue
Block a user