Skip to content
Browse files

initial commit

  • Loading branch information...
0 parents commit a49122192e3e34aa35ace840a60cbadd01466e06 @branch14 committed Mar 27, 2012
Showing with 583 additions and 0 deletions.
  1. +7 −0 .gitignore
  2. +81 −0 .rvmrc
  3. +4 −0 Gemfile
  4. +21 −0 LICENSE
  5. +37 −0 README.md
  6. +2 −0 Rakefile
  7. 0 TODO
  8. +2 −0 lib/rack_r.rb
  9. +181 −0 lib/rack_r/middleware.rb
  10. +23 −0 lib/rack_r/railtie.rb
  11. +3 −0 lib/rack_r/version.rb
  12. +20 −0 rack-r.gemspec
  13. +38 −0 test/example.r
  14. +11 −0 test/helper.rb
  15. +153 −0 test/test_rack_r.rb
7 .gitignore
@@ -0,0 +1,7 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
+*~
+.#*
+\#*
81 .rvmrc
@@ -0,0 +1,81 @@
+#!/usr/bin/env bash
+
+# This is an RVM Project .rvmrc file, used to automatically load the ruby
+# development environment upon cd'ing into the directory
+
+# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional.
+environment_id="ruby-1.8.7-p357@rack-r"
+
+#
+# Uncomment the following lines if you want to verify rvm version per project
+#
+# rvmrc_rvm_version="1.10.2" # 1.10.1 seams as a safe start
+# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
+# echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
+# return 1
+# }
+#
+
+#
+# Uncomment following line if you want options to be set only for given project.
+#
+# PROJECT_JRUBY_OPTS=( --1.9 )
+#
+# The variable PROJECT_JRUBY_OPTS requires the following to be run in shell:
+#
+# chmod +x ${rvm_path}/hooks/after_use_jruby_opts
+#
+
+#
+# First we attempt to load the desired environment directly from the environment
+# file. This is very fast and efficient compared to running through the entire
+# CLI and selector. If you want feedback on which environment was used then
+# insert the word 'use' after --create as this triggers verbose mode.
+#
+if [[ -d "${rvm_path:-$HOME/.rvm}/environments" \
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
+then
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
+
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]
+ then
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
+ fi
+else
+ # If the environment file has not yet been created, use the RVM CLI to select.
+ if ! rvm --create use "$environment_id"
+ then
+ echo "Failed to create RVM environment '${environment_id}'."
+ return 1
+ fi
+fi
+
+#
+# If you use an RVM gemset file to install a list of gems (*.gems), you can have
+# it be automatically loaded. Uncomment the following and adjust the filename if
+# necessary.
+#
+# filename=".gems"
+# if [[ -s "$filename" ]]
+# then
+# rvm gemset import "$filename" | grep -v already | grep -v listed | grep -v complete | sed '/^$/d'
+# fi
+
+# If you use bundler, this might be useful to you:
+# if [[ -s Gemfile ]] && ! command -v bundle >/dev/null
+# then
+# printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
+# gem install bundler
+# fi
+# if [[ -s Gemfile ]] && command -v bundle
+# then
+# bundle install
+# fi
+
+if [[ $- == *i* ]] # check for interactive shells
+then
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
+else
+ echo "Using: $GEM_HOME" # don't use colors in interactive shells
+fi
+
4 Gemfile
@@ -0,0 +1,4 @@
+source "http://rubygems.org"
+
+# Specify your gem's dependencies in aizuchi.gemspec
+gemspec
21 LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2011 Phil Hofmann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
37 README.md
@@ -0,0 +1,37 @@
+RackR - Use R in your Rack stack
+================================
+
+Install in Rails
+----------------
+
+Put the following in your Gemfile and run `bundle install` or let
+[Guard](https://github.com/guard/guard-bundler) kick in.
+
+ gem 'rack-r'
+
+Using RackR outside of Rails
+----------------------------
+
+ require 'rack_r/middleware'
+ use RackR::Middleware, :config => 'path/to/config/rack-r.yml'
+
+Configure
+---------
+
+RackR will create a sample config file in `config/rack-r.yml`.
+
+Dependencies
+------------
+
+ apt-get install r-base
+
+Patches and the like
+--------------------
+
+If you run into bugs, have suggestions, patches or want to use RackR
+with something else than Rails feel free to drop me a line.
+
+License
+-------
+
+RackR is released under MIT License, see LICENSE.
2 Rakefile
@@ -0,0 +1,2 @@
+require 'bundler'
+Bundler::GemHelper.install_tasks
0 TODO
No changes.
2 lib/rack_r.rb
@@ -0,0 +1,2 @@
+require "rack_r/railtie" if defined? Rails
+
181 lib/rack_r/middleware.rb
@@ -0,0 +1,181 @@
+require 'ostruct'
+require 'yaml'
+require 'erb'
+
+module RackR
+ class Middleware < Struct.new :app, :options
+
+ def call(env)
+ @env = env
+ return call_app unless config.enabled
+ if get? and md = match_path
+ process(md.to_a.last)
+ else
+ extract
+ end
+ end
+
+ def call_app
+ app.call(@env)
+ end
+
+ def extract
+ status, headers, response = call_app
+ if headers["Content-Type"] =~ /text\/html|application\/xhtml\+xml/
+ # TODO if contains <script type="text/r">
+ body = ""
+ response.each { |part| body << part }
+ while md = body.match(node_regex)
+ tempdir = temp_dir
+ key = File.basename(tempdir)
+ r_script = config.r_header + md.to_a.last
+ write_file(File.join(tempdir, config.r_file), r_script)
+ body.sub!(node_regex, ajaxer(key))
+ end
+ headers["Content-Length"] = body.length.to_s
+ response = [ body ]
+ end
+ [ status, headers, response ]
+ end
+
+ def process(key)
+ path = temp_dir(key)
+ return call_app unless File.directory?(path) # TODO render error msg
+ exec_r_script(config.r_file, path)
+ files = Dir.entries(path).sort
+ # build body
+ body = [ config.html.prefix ]
+ files.each do |file|
+ config.templates.each do |template|
+ if file =~ Regexp.new(template['pattern'], Regexp::IGNORECASE)
+ url = "#{config.public_url}/#{file}"
+ src = File.join(path, file)
+ dst = File.join(public_path, file)
+ FileUtils.cp(src, dst)
+ body << ERB.new(template['template']).result(binding)
+ end
+ end
+ end
+ body << config.html.suffix
+ [ 200, { "Content-Type" => "text/html" }, body ]
+ end
+
+ # return true if request method is GET
+ def get?
+ @env["REQUEST_METHOD"] == "GET"
+ end
+
+ def path_regex
+ Regexp.new "^#{config.url_scope}/(.*)"
+ end
+
+ def node_regex
+ opts = Regexp::IGNORECASE + Regexp::MULTILINE
+ Regexp.new config.node_regex, opts
+ end
+
+ def path_info
+ @env["PATH_INFO"]
+ end
+
+ def match_path
+ path_info.match(path_regex)
+ end
+
+ def exec_r_script(file, path=nil) # TODO track time
+ cmd = path.nil? ? "R CMD BATCH #{file}" :
+ "(cd #{path} && R CMD BATCH #{file})"
+ %x[#{cmd}]
+ end
+
+ def temp_dir(key=nil)
+ return Dir.mktmpdir(config.temp.prefix, config.temp.dir) if key.nil?
+ File.join config.temp.dir, key
+ end
+
+ def write_file(path, content)
+ ::File.open(path, 'w') { |f| f.puts content }
+ end
+
+ def public_path
+ config.public_path.tap do |path|
+ FileUtils.mkdir_p(path) unless File.directory?(path)
+ end
+ end
+
+ def ajaxer(key)
+ ERB.new(config.ajaxer).result(binding)
+ end
+
+ def default_config
+ ::File.read(__FILE__).gsub(/.*__END__\n/m, '')
+ end
+
+ def config
+ return @config unless @config.nil?
+ path = options[:config]
+ raise "no config path given" unless path
+ write_file(path, default_config) unless ::File.exist?(path)
+ @config = deep_ostruct(::File.open(path) { |yf| YAML::load(yf) })
+ end
+
+ private
+
+ def deep_ostruct(hash)
+ OpenStruct.new(hash).tap do |ostruct|
+ hash.keys.each do |key|
+ if ostruct.send(key).is_a?(Hash)
+ ostruct.send("#{key}=", deep_ostruct(hash[key]))
+ end
+ end
+ end
+ end
+
+ end
+end
+
+__END__
+---
+# RackR -- process R on the fly
+# RackR depends on jQuery >= 1.4.4
+#
+# changes to this file will not be picked up
+# without a restart of your app
+#
+enabled: true
+url_scope: /rack-r
+public_path: public/system/rack-r
+public_url: /system/rack-r
+temp:
+ dir: /tmp
+ prefix: rr_
+r_file: script.r
+r_header: |
+ # connect to database
+ db <- 'code goes here'
+ajaxer: |
+ <div class='rack_r' id='<%= key %>'>Processing R (<%= key %>)&ellipsis;</div>
+ <script type='text/javascript'>
+ var url = '<%= config.url_scope %>/<%= key %>';
+ $.ajax(url, update: '<%= key %>');
+ </script>
+html:
+ prefix: <div class='rack_r'>
+ suffix: </div>
+node_regex: <script\s+type=['"]text/r['"]\s*>(.*)</script>
+templates:
+ - pattern: .(jpg|jpeg|png)$
+ template: |
+ <img src='<%= url %>' />
+ - pattern: .csv$
+ template: |
+ <a href='<%= url %>'><%= file %></a>
+#
+# uncomment the following two lines, if your project
+# doesn't use jquery already
+# javascripts:
+# - http://code.jquery.com/jquery-1.5.1.min.js
+# or this line if you want to ship it yourself
+# - /javascripts/jquery-1.5.1.min.js
+# uncomment the following line, in case your project uses prototype
+# jquery_noconflict: true
23 lib/rack_r/railtie.rb
@@ -0,0 +1,23 @@
+require 'rack_r/middleware'
+
+module RackR
+
+ case Rails.version.to_i
+ when 2
+ Rails.configuration.middleware.use RackR::Middleware,
+ :config => File.expand_path('config/rack-r.yml', RAILS_ROOT)
+
+ when 3
+ class Railtie < Rails::Railtie
+ initializer "rack_r.insert_middleware" do |app|
+ app.config.middleware.use "RackR::Middleware",
+ :config => File.expand_path('config/rack-r.yml', Rails.root)
+ end
+ end
+
+ else
+ raise "Unknown Rails version"
+ end
+
+end
+
3 lib/rack_r/version.rb
@@ -0,0 +1,3 @@
+module RackR
+ VERSION = "0.1.0"
+end
20 rack-r.gemspec
@@ -0,0 +1,20 @@
+$:.push File.expand_path("../lib", __FILE__)
+require "rack_r/version"
+
+Gem::Specification.new do |s|
+ s.name = "rack-r"
+ s.version = RackR::VERSION
+ s.platform = Gem::Platform::RUBY
+ s.authors = ["Phil Hofmann"]
+ s.email = ["phil@branch14.org"]
+ s.homepage = "http://branch14.org/rack-r"
+ s.summary = %q{Use R in your Rack stack}
+ s.description = %q{Use R in your Rack stack}
+
+ # s.rubyforge_project = "rack-r"
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib", "rails"]
+end
38 test/example.r
@@ -0,0 +1,38 @@
+# y mntl. Rate
+# f Franchise
+# s Selbstbehalt
+# m Selbstbehalt max.
+
+png('insurance.png')
+
+costf <- function(y, f, s=0.1, m=700) {
+ return(function(x) {
+ (y * 12) + min(f, x) + max(0, min(m - f, s * (max(f, x) - f)))
+ })
+}
+
+f0 <- costf( 95.70, 0 )
+f1 <- costf( 90.00, 100 )
+f2 <- costf( 84.10, 200 )
+f4 <- costf( 72.40, 400 )
+f6 <- costf( 60.70, 600 )
+
+x_max <- 7250
+x <- seq(0, x_max, by=10)
+plot(x, x, type='l', lty=3,
+ xlim=range(0, x_max),
+ ylim=range(500, 2000),
+ xlab='Kosten Verursacht',
+ ylab='Kosten Gezahlt')
+
+lines(x, apply(array(x), 1, f0), col='green')
+lines(x, apply(array(x), 1, f1))
+lines(x, apply(array(x), 1, f2))
+lines(x, apply(array(x), 1, f4))
+lines(x, apply(array(x), 1, f6), col='red')
+
+f <- function(x) { return(f0(x) - f6(x)) }
+lower <- uniroot(f, c(400, 600))
+upper <- uniroot(f, c(2500, 3000))
+abline(v=lower$root, lty=2)
+abline(v=upper$root, lty=2)
11 test/helper.rb
@@ -0,0 +1,11 @@
+require 'rubygems'
+require 'test/unit'
+require 'shoulda'
+
+$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
+$LOAD_PATH.unshift(File.dirname(__FILE__))
+require 'rack_r'
+require 'rack_r/middleware'
+
+class Test::Unit::TestCase
+end
153 test/test_rack_r.rb
@@ -0,0 +1,153 @@
+require File.expand_path('../helper', __FILE__)
+require 'rack/mock'
+
+class TestRackR < Test::Unit::TestCase
+
+ CONFIGFILE = '/tmp/test_rack_r'
+ File.unlink(CONFIGFILE) if File.exist?(CONFIGFILE)
+
+ EXPECTED_CODE = /class='rack_r'/
+
+ context "Util" do
+ should "create proper temp dir" do
+ td = app.temp_dir
+ assert File.directory?(td)
+
+ base = File.basename(td)
+ assert_equal td, app.temp_dir(base)
+
+ assert FileUtils.rm_rf(td)
+ assert !File.exist?(td)
+ end
+ end
+
+ context "Proper regexs" do
+ should "detect r code if present" do
+ assert_match app.node_regex, HTML_POSITIVE
+ end
+
+ should "not detect r code if none is present" do
+ assert_no_match app.node_regex, HTML_NEGATIVE
+ end
+ end
+
+ context "Embedding payload" do
+ should "place the payload in body of an HTML request" do
+ assert_match EXPECTED_CODE, request.body
+ end
+
+ should "place the payload in body of an XHTML request" do
+ response = request(:content_type => 'application/xhtml+xml')
+ assert_match EXPECTED_CODE, response.body
+ end
+
+ should "not place the payload in a non HTML request" do
+ response = request(:content_type => 'application/xml', :body => [XML])
+ assert_no_match EXPECTED_CODE, response.body
+ end
+
+ should "not place the playload in a document not containing r code" do
+ response = request(:body => HTML_NEGATIVE)
+ assert_no_match EXPECTED_CODE, response.body
+ end
+ end
+
+ context "Processing R" do
+ should 'detect get request' do
+ request(:path => "/rack-r/rr-some-random-key")
+ assert @app.get?
+ end
+
+ should 'detect path' do
+ request(:path => "/rack-r/rr-some-random-key")
+ assert @app.match_path
+ end
+
+ should 'properly respond to rack_r request' do
+ key = 'rr-some-key'
+ path = app.temp_dir(key)
+ FileUtils.rm_rf(path) if File.exist?(path)
+ Dir.mkdir(path)
+ src = File.expand_path('../example.r', __FILE__)
+ dst = File.join(path, app.config.r_file)
+ FileUtils.cp(src, dst)
+ response = request(:path => "/rack-r/#{key}")
+ assert Dir.entries(path).include?('insurance.png')
+ regex = Regexp.new("src='#{@app.config.public_url}/insurance.png'")
+ assert_match regex, response.body
+ FileUtils.rm_rf(path)
+ end
+ end
+
+ # context "Deliver payload" do
+ # setup do
+ # @expected_file = File.expand_path(File.join(%w(.. .. public) << PAYLOAD), __FILE__)
+ # @response = request({}, "/#{PAYLOAD}")
+ # end
+ #
+ # should "deliver #{PAYLOAD}" do
+ # expected = File.read(@expected_file)
+ # assert expected, @response.body
+ # end
+ #
+ # should "set the content-type correctly" do
+ # assert 'text/javascript', @response.body
+ # end
+ # end
+
+ private
+
+ HTML_POSITIVE = <<-EOHTML
+ <html>
+ <head>
+ <title>Sample Page</title>
+ </head>
+ <body>
+ <h2>Here goes some R</h2>
+ <script type='text/r'>
+ some invalid r code
+ </script>
+ </body>
+ </html>
+ EOHTML
+
+ HTML_NEGATIVE = <<-EOHTML
+ <html>
+ <head>
+ <title>Sample page with no matching node</title>
+ </head>
+ <body>
+ <p>Empty page.</p>
+ </body>
+ </html>
+ EOHTML
+
+ XML = <<-EOXML
+ <?xml version="1.0" encoding="ISO-8859-1"?>
+ <user>
+ <name>Some Name</name>
+ <age>Some Age</age>
+ </user>
+ EOXML
+
+ def request(options={})
+ path = options.delete(:path) || '/'
+ @app = app(options)
+ request = Rack::MockRequest.new(@app).get(path)
+ yield(@app, request) if block_given?
+ request
+ end
+
+ def app(options={})
+ options = options.clone
+ options[:content_type] ||= "text/html"
+ options[:body] ||= [ HTML_POSITIVE ]
+ rack_app = lambda do |env|
+ [ 200,
+ { 'Content-Type' => options.delete(:content_type) },
+ options.delete(:body) ]
+ end
+ RackR::Middleware.new(rack_app, :config => CONFIGFILE)
+ end
+
+end

0 comments on commit a491221

Please sign in to comment.
Something went wrong with that request. Please try again.