# == About camping.rb
#
# Camping comes with two versions of its source code.  The code contained in
# lib/camping.rb is compressed, stripped of whitespace, using compact
algorithms
# to keep it tight.  The unspoken rule is that camping.rb should be
flowed with
# no more than 80 characters per line and must not exceed four kilobytes.
#
# On the other hand, lib/camping-unabridged.rb contains the same code,
laid out
# nicely with piles of documentation everywhere. This documentation is
entirely
# generated from lib/camping-unabridged.rb using RDoc and our "flipbook"
template
# found in the extras directory of any camping distribution.
require "uri"
require "rack"

$LOADED_FEATURES << "camping.rb"

class Object #:nodoc:
  def meta_def(m,&b) #:nodoc:
    (class<<self;self end).send(:define_method,m,&b)
  end
end

# If you're new to Camping, you should probably start by reading the first
# chapters of {The Camping Book}[file:book/01_introduction.html#toc].
#
# Okay. So, the important thing to remember is that <tt>Camping.goes
:Nuts</tt>
# copies the Camping module into Nuts. This means that you should never use
# any of these methods/classes on the Camping module, but rather on your own
# app. Here's a short explanation on how Camping is organized:
#
# * Camping::Controllers is where your controllers live.
# * Camping::Models is where your models live.
# * Camping::Views is where your views live.
# * Camping::Base is a module which is included in all your controllers.
# * Camping::Helpers is a module with useful helpers, both for the
controllers
#   and the views. You should fill this up with your own helpers.
#
# Camping also ships with:
#
# * Camping::Session adds states to your app.
# * Camping::Server starts up your app in development.
# * Camping::Reloader automatically reloads your apps when a file has
changed.
#
# More importantly, Camping also installs The Camping Server,
# please see Camping::Server.
module Camping
  C = self
  S = IO.read(__FILE__) rescue nil
  P = "<h1>Cam\ping Problem!</h1><h2>%s</h2>"
  U = Rack::Utils
  O = {}
  Apps = []
  SK = :camping #Key for r.session
  # An object-like Hash.
  # All Camping query string and cookie variables are loaded as this.
  #
  # To access the query string, for instance, use the <tt>@input</tt>
variable.
  #
  #   module Blog::Controllers
  #     class Index < R '/'
  #       def get
  #         if (page = @input.page.to_i) > 0
  #           page -= 1
  #         end
  #         @posts = Post.all, :offset => page * 20, :limit => 20
  #         render :index
  #       end
  #     end
  #   end
  #
  # In the above example if you visit <tt>/?page=2</tt>, you'll get the
second
  # page of twenty posts.  You can also use <tt>@input['page']</tt> to
get the
  # value for the <tt>page</tt> query variable.
  class H < Hash
    # Gets or sets keys in the hash.
    #
    #   @cookies.my_favorite = :macadamian
    #   @cookies.my_favorite
    #   => :macadamian
    #
    def method_missing(m,*a)
        m.to_s=~/=$/?self[$`]=a[0]:a==[]?self[m.to_s]:super
    end
    undef id, type if ?? == 63
  end

  class Cookies < H
    attr_accessor :_p
    #
    # Cookies that are set at this response
    def _n; @n ||= {} end

    alias _s []=

    def set(k, v, o = {})
      _s(j=k.to_s, v)
      _n[j] = {:value => v, :path => _p}.update(o)
    end

    def []=(k, v)
      set k, v, v.is_a?(Hash) ? v : {}
    end
  end

  # Helpers contains methods available in your controllers and views.
You may
  # add methods of your own to this module, including many helper
methods from
  # Rails. This is analogous to Rails' <tt>ApplicationHelper</tt> module.
  #
  # == Using ActionPack Helpers
  #
  # If you'd like to include helpers from Rails' modules, you'll need to
look
  # up the helper module in the Rails documentation at
http://api.rubyonrails.org/.
  #
  # For example, if you look up the
<tt>ActionView::Helpers::FormTagHelper</tt>
  # class, you'll find that it's loaded from the
<tt>action_view/helpers/form_tag_helper.rb</tt>
  # file. You'll need to have the ActionPack gem installed for this to work.
  #
  # A helper often depends on other helpers, so you would have to look up
  # the dependencies too. <tt>FormTagHelper</tt> for instance required the
  # <tt>content_tag</tt> provided by <tt>TagHelper</tt>.
  #
  #   require 'action_view/helpers/form_tag_helper'
  #
  #   module Nuts::Helpers
  #     include ActionView::Helpers::TagHelper
  #     include ActionView::Helpers::FormTagHelper
  #   end
  #
  # == Return a response immediately
  # If you need to return a response inside a helper, you can use
<tt>throw :halt</tt>.
  #
  #   module Nuts::Helpers
  #     def requires_login!
  #       unless @state.user_id
  #         redirect Login
  #         throw :halt
  #       end
  #     end
  #   end
  #
  #   module Nuts::Controllers
  #     class Admin
  #       def get
  #         requires_login!
  #         "Never gets here unless you're logged in"
  #       end
  #     end
  #   end
  module Helpers
    # From inside your controllers and views, you will often need to
figure out
    # the route used to get to a certain controller +c+. Pass the controller
    # class and any arguments into the R method, a string containing the
route
    # will be returned to you.
    #
    # Assuming you have a specific route in an edit controller:
    #
    #   class Edit < R '/edit/(\d+)'
    #
    # A specific route to the Edit controller can be built with:
    #
    #   R(Edit, 1)
    #
    # Which outputs: <tt>/edit/1</tt>.
    #
    # If a controller has many routes, the route will be selected if it
is the
    # first in the routing list to have the right number of arguments.
    #
    # == Using R in the View
    #
    # Keep in mind that this route doesn't include the root path. You will
    # need to use <tt>/</tt> (the slash method above) in your controllers.
    # Or, go ahead and use the Helpers#URL method to build a complete
URL for
    # a route.
    #
    # However, in your views, the :href, :src and :action attributes
    # automatically pass through the slash method, so you are encouraged to
    # use <tt>R</tt> or <tt>URL</tt> in your views.
    #
    #  module Nuts::Views
    #    def menu
    #      div.menu! do
    #        a 'Home', :href => URL()
    #        a 'Profile', :href => "/profile"
    #        a 'Logout', :href => R(Logout)
    #        a 'Google', :href => 'http://google.com'
    #      end
    #    end
    #  end
    #
    # Let's say the above example takes place inside an application
mounted at
    # <tt>http://localhost:3301/frodo</tt> and that a controller named
    # <tt>Logout</tt> is assigned to route <tt>/logout</tt>.
    # The HTML will come out as:
    #
    #   <div id="menu">
    #     <a href="http://localhost:3301/frodo/";>Home</a>
    #     <a href="/frodo/profile">Profile</a>
    #     <a href="/frodo/logout">Logout</a>
    #     <a href="http://google.com";>Google</a>
    #   </div>
    #
    def R(c,*g)
      p,h=/\(.+?\)/,g.grep(Hash)
      g-=h
      raise "bad route" if !u = c.urls.find{|x|
        break x if x.scan(p).size == g.size &&
          /^#{x}\/?$/ =~ (x=g.inject(x){|x,a|
            x.sub p,U.escape((a.to_param rescue a))}.gsub(/\\(.)/){$1})
      }
      h.any?? u+"?"+U.build_query(h[0]) : u
    end

    # Simply builds a complete path from a path +p+ within the app.  If your
    # application is mounted at <tt>/blog</tt>:
    #
    #   self / "/view/1"    #=> "/blog/view/1"
    #   self / "styles.css" #=> "styles.css"
    #   self / R(Edit, 1)   #=> "/blog/edit/1"
    #
    def /(p); p[0] == ?/ ? @root + p : p end

    # Builds a URL route to a controller or a path, returning a URI object.
    # This way you'll get the hostname and the port number, a complete URL.
    #
    # You can use this to grab URLs for controllers using the R-style
syntax.
    # So, if your application is mounted at <tt>http://test.ing/blog/</tt>
    # and you have a View controller which routes as <tt>R
'/view/(\d+)'</tt>:
    #
    #   URL(View, @post.id)    #=> #<URL:http://test.ing/blog/view/12>
    #
    # Or you can use the direct path:
    #
    #   self.URL               #=> #<URL:http://test.ing/blog/>
    #   self.URL + "view/12"   #=> #<URL:http://test.ing/blog/view/12>
    #   URL("/view/12")        #=> #<URL:http://test.ing/blog/view/12>
    #
    # It's okay to pass URL strings through this method as well:
    #
    #   URL("http://google.com";)  #=> #<URL:http://google.com>
    #
    # Any string which doesn't begin with a slash will pass through
    # unscathed.
    def URL c='/',*a
      c = R(c, *a) if c.respond_to? :urls
      c = self/c
      c = @request.url[/.{8,}?(?=\/|$)/]+c if c[0]==?/
      URI(c)
    end
  end

  # Camping::Base is built into each controller by way of the generic
routing
  # class Camping::R. In some ways, this class is trying to do too much, but
  # it saves code for all the glue to stay in one place. Forgivable,
  # considering that it's only really a handful of methods and accessors.
  #
  # Everything in this module is accessible inside your controllers.
  module Base
    attr_accessor :env, :request, :root, :input, :cookies, :state,
                  :status, :headers, :body

    T = {}
    L = :layout

    # Finds a template, returning either:
    #
    #   false             # => Could not find template
    #   true              # => Found template in Views
    #   instance of Tilt  # => Found template in a file
    def lookup(n)
      T.fetch(n.to_sym) do |k|
        t = Views.method_defined?(k) ||
          (t = O[:_t].keys.grep(/^#{n}\./)[0]and
Template[t].new{O[:_t][t]}) ||
          (f = Dir[[O[:views] || "views", "#{n}.*"]*'/'][0]) &&
          Template.new(f, O[f[/\.(\w+)$/, 1].to_sym] || {})

        O[:dynamic_templates] ? t : T[k] = t
      end
    end

    # Display a view, calling it by its method name +v+. If a
<tt>layout</tt>
    # method is found in Camping::Views, it will be used to wrap the HTML.
    #
    #   module Nuts::Controllers
    #     class Show
    #       def get
    #         @posts = Post.find :all
    #         render :index
    #       end
    #     end
    #   end
    #
    def render(v, *a, &b)
      if t = lookup(v)
        # Has this controller rendered before?
        r = @_r
        # Set @_r to truthy value
        @_r = (o = Hash === a[-1] ? a.pop : {})
        s = (t == true) ? mab { send(v, *a, &b) } : t.render(self,
o[:locals] || {}, &b)
        s = render(L, o.merge(L => false)) { s } if o[L] or o[L].nil? &&
lookup(L) && !r && v.to_s[0] != ?_
        s
      else
        raise "no template: #{v}"
      end
    end

    # You can directly return HTML from your controller for quick debugging
    # by calling this method and passing some Markaby to it.
    #
    #   module Nuts::Controllers
    #     class Info
    #       def get; mab{ code @headers.inspect } end
    #     end
    #   end
    #
    # You can also pass true to use the :layout HTML wrapping method
    def mab(&b)
      extend Mab
      mab(&b)
    end

    # A quick means of setting this controller's status, body and headers
    # based on a Rack response:
    #
    #   r(302, 'Location' => self / "/view/12", '')
    #   r(*another_app.call(@env))
    #
    # You can also switch the body and the header if you want:
    #
    #   r(404, "Could not find page")
    #
    # See also: #r404, #r500 and #r501
    def r(s, b, h = {})
      b, h = h, b if Hash === b
      @status = s
      @headers.merge!(h)
      @body = b
    end

    # Formulate a redirect response: a 302 status with <tt>Location</tt>
header
    # and a blank body. Uses Helpers#URL to build the location from a
    # controller route or path.
    #
    # So, given a root of <tt>http://localhost:3301/articles</tt>:
    #
    #   redirect "view/12"  # redirects to
"//localhost:3301/articles/view/12"
    #   redirect View, 12   # redirects to
"//localhost:3301/articles/view/12"
    #
    # <b>NOTE:</b> This method doesn't magically exit your methods and
redirect.
    # You'll need to <tt>return redirect(...)</tt> if this isn't the
last statement
    # in your code, or <tt>throw :halt</tt> if it's in a helper.
    #
    # See: Controllers
    def redirect(*a)
      r(302,'','Location'=>URL(*a).to_s)
    end

    # Called when a controller was not found. You can override this if you
    # want to customize the error page:
    #
    #   module Nuts
    #     def r404(path)
    #       @path = path
    #       render :not_found
    #     end
    #   end
    def r404(p)
      P % "#{p} not found"
    end

    # Called when an exception is raised. However, if there is a parse error
    # in Camping or in your application's source code, it will not be
caught.
    #
    # +k+ is the controller class, +m+ is the request method (GET, POST,
etc.)
    # and +e+ is the Exception which can be mined for useful info.
    #
    # By default this simply re-raises the error so a Rack middleware can
    # handle it, but you are free to override it here:
    #
    #   module Nuts
    #     def r500(klass, method, exception)
    #       send_email_alert(klass, method, exception)
    #       render :server_error
    #     end
    #   end
    def r500(k,m,e)
      raise e
    end

    # Called if an undefined method is called on a controller, along
with the
    # request method +m+ (GET, POST, etc.)
    def r501(m)
      P % "#{m.upcase} not implemented"
    end

    # Serves the string +c+ with the MIME type of the filename +p+.
    # Defaults to text/html.
    def serve(p, c)
      t = Rack::Mime.mime_type(p[/\..*$/], "text/html") and
@headers["Content-Type"] = t
      c
    end

    # Turn a controller into a Rack response. This is designed to be used to
    # pipe controllers into the <tt>r</tt> method. A great way to
forward your
    # requests!
    #
    #   class Read < '/(\d+)'
    #     def get(id)
    #       Post.find(id)
    #     rescue
    #       r *Blog.get(:NotFound, @headers.REQUEST_URI)
    #     end
    #   end
    def to_a
      @env['rack.session'][SK] = Hash[@state]
      r = Rack::Response.new(@body, @status, @headers)
      @cookies._n.each do |k, v|
        r.set_cookie(k, v)
      end
      r.to_a
    end

    def initialize(env, m) #:nodoc:
      r = @request = Rack::Request.new(@env = env)
      @root, @input, @cookies, @state,
      @headers, @status, @method =
      r.script_name.sub(/\/$/,''), n(r.params),
      Cookies[r.cookies], H[r.session[SK]||{}],
      {'Content-Type'=>'text/html'}, m =~ /r(\d+)/ ? $1.to_i : 200, m
      @cookies._p = self/"/"
    end

    def n(h) # :nodoc:
      if Hash === h
        h.inject(H[]) do |m, (k, v)|
          m[k] = n(v)
          m
        end
      else
        h
      end
    end

    # All requests pass through this method before going to the controller.
    # Some magic in Camping can be performed by overriding this method.
    def service(*a)
      r = catch(:halt){send(@method, *a)}
      @body ||= r
      self
    end
  end


  # Controllers receive the requests and send a response back to the client.
  # A controller is simply a class which must implement the HTTP methods it
  # wants to accept:
  #
  #   module Nuts::Controllers
  #     class Index
  #       def get
  #         "Hello World"
  #       end
  #     end
  #
  #     class Posts
  #       def post
  #         Post.create(@input)
  #         redirect Index
  #       end
  #     end
  #   end
  #
  # == Defining a controller
  #
  # There are two ways to define controllers:
  #
  # 1. Define a class and let Camping figure out the route.
  # 2. Add the route explicitly using R.
  #
  # If you don't use R, Camping will first split the controller name up by
  # words (HelloWorld => Hello and World).
  #
  # After that, it will do the following:
  #
  # * Replace Index with /
  # * Replace X with ([^/]+)
  # * Replace N with (\\\d+)
  # * Turn everything else into lowercase
  # * Join the words with slashes
  #
  #--
  # NB!  N will actually be replaced with (\d+), but it needs to be escaped
  # here in order to work correctly with RDoc.
  #++
  #
  # Here are a few examples:
  #
  #   Index   # => /
  #   PostN   # => /post/(\d+)
  #   PageX   # => /page/([^/]+)
  #   Pages   # => /pages
  #
  # == The request
  #
  # The following variables aid in describing request:
  #
  # * @env contains the environment as defined in
http://rack.rubyforge.org/doc/SPEC.html
  # * @request is Rack::Request.new(@env)
  # * @root is the path where the app is mounted
  # * @cookies is a hash with the cookies sent by the client
  # * @state is a hash with the sessions (see Camping::Session)
  # * @method is the HTTP method in lowercase
  #
  # == The response
  #
  # You can change these variables to your needs:
  #
  # * @status is the HTTP status (defaults to 200)
  # * @headers is a hash with the headers
  # * @body is the body (a string or something which responds to #each)
  # * Any changes in @cookies and @state will also be sent to the client
  #
  # If you haven't set @body, it will use the return value of the method:
  #
  #   module Nuts::Controllers
  #     class Index
  #       def get
  #         "This is the body"
  #       end
  #     end
  #
  #     class Posts
  #       def get
  #         @body = "Hello World!"
  #         "This is ignored"
  #       end
  #     end
  #   end
  module Controllers
    @r = []
    class << self
      # Add routes to a controller class by piling them into the R method.
      #
      # The route is a regexp which will match the request path. Anything
      # enclosed in parenthesis will be sent to the method as arguments.
      #
      #   module Camping::Controllers
      #     class Edit < R '/edit/(\d+)', '/new'
      #       def get(id)
      #         if id   # edit
      #         else    # new
      #         end
      #       end
      #     end
      #   end
      def R *u
        r=@r
        Class.new {
          meta_def(:urls){u}
          meta_def(:inherited){|x|r<<x}
        }
      end

      # Dispatch routes to controller classes.
      # For each class, routes are checked for a match based on their
order in the routing list
      # given to Controllers::R. If no routes were given, the dispatcher
uses a slash followed
      # by the lowercased name of the controller.
      #
      # Controllers are searched in this order:
      #
      # * Classes without routes, since they refer to a very specific URL.
      # * Classes with routes are searched in order of their creation.
      #
      # So, define your catch-all controllers last.
      def D(p, m, e)
        p = '/' if !p || !p[0]
        a=O[:_t].find{|n,_|n==p} and return [I, :serve, *a]
        @r.map { |k|
          k.urls.map { |x|
            return (k.method_defined?(m)) ?
              [k, m, *$~[1..-1].map{|x|U.unescape(x)}] : [I, 'r501', m]
if p =~ /^#{x}\/?$/
          }
        }
        [I, 'r404', p]
      end

      N = H.new { |_,x| x.downcase }.merge! "N" => '(\d+)', "X" =>
'([^/]+)', "Index" => ''
      # The route maker, called by Camping internally.
      #
      # Still, it's worth know what this method does. Since Ruby doesn't
keep
      # track of class creation order, we're keeping an internal list of the
      # controllers which inherit from R(). This method goes through and
adds
      # all the remaining routes to the beginning of the list and
ensures all
      # the controllers have the right mixins.
      #
      # Anyway, if you are calling the URI dispatcher from outside of a
      # Camping server, you'll definitely need to call this to set
things up.
      # Don't call it too early though - any controllers added after this
      # method was called won't work properly.
      def M
        def M #:nodoc:
        end
        constants.map { |c|
          k = const_get(c)
          k.send :include,C,X,Base,Helpers,Models
          @r=[k]+@r if @r-[k]==@r

k.meta_def(:urls){["/#{c.to_s.scan(/.[^A-Z]*/).map(&N.method(:[]))*'/'}"]}if
!k.respond_to?:urls
        }
      end
    end

    # Internal controller with no route. Used to show internal messages.
    I = R()
  end
  X = Controllers

  class << self
    # When you are running multiple applications, you may want to create
    # independent modules for each Camping application. Camping::goes
    # defines a toplevel constant with the whole MVC rack inside:
    #
    #   require 'camping'
    #   Camping.goes :Nuts
    #
    #   module Nuts::Controllers; ... end
    #   module Nuts::Models;      ... end
    #   module Nuts::Views;       ... end
    #
    # Additionally, you can pass a Binding as the second parameter,
    # which enables you to create a Camping-based application within
    # another module.
    #
    # Here's an example of namespacing your web interface and
    # code for a worker process together:
    #
    #   module YourApplication
    #     Camping.goes :Web, binding()
    #     module Web
    #       ...
    #     end
    #     module Worker
    #       ...
    #     end
    #   end
    #
    # All the applications will be available in Camping::Apps.
    def goes(m, g=TOPLEVEL_BINDING)
      Apps << a = eval(S.gsub(/Camping/,m.to_s), g)
      caller[0]=~/:/
      IO.read(a.set:__FILE__,$`)=~/^__END__/ &&
      (b=$'.split(/^@@\s*(.+?)\s*\r?\n/m)).shift rescue nil
      a.set :_t,H[*b||[]]
    end

    # Ruby web servers use this method to enter the Camping realm. The +e+
    # argument is the environment variables hash as per the Rack
specification.
    # Array with [status, headers, body] is expected at the output.
    #
    # See: http://rack.rubyforge.org/doc/SPEC.html
    def call(e)
      X.M
      k,m,*a=X.D e["PATH_INFO"],e['REQUEST_METHOD'].downcase,e
      k.new(e,m).service(*a).to_a
    rescue
      r500(:I, k, m, $!, :env => e).to_a
    end

    # The Camping scriptable dispatcher. Any unhandled method call to
the app
    # module will be sent to a controller class, specified as an argument.
    #
    #   Blog.get(:Index)
    #   #=> #<Blog::Controllers::Index ... >
    #
    # The controller object contains all the @cookies, @body, @headers, etc.
    # formulated by the response.
    #
    # You can also feed environment variables and query variables as a hash,
    # the final argument.
    #
    #   Blog.post(:Login, :input => {'username' => 'admin', 'password'
=> 'camping'})
    #   #=> #<Blog::Controllers::Login @user=... >
    #
    #   Blog.get(:Info, :env => {'HTTP_HOST' => 'wagon'})
    #   #=> #<Blog::Controllers::Info @headers={'HTTP_HOST'=>'wagon'} ...>
    #
    def method_missing(m, c, *a)
      X.M
      h = Hash === a[-1] ? a.pop : {}
      e = H[Rack::MockRequest.env_for('',h.delete(:env)||{})]
      k = X.const_get(c).new(e,m.to_s)
      h.each { |i, v| k.send("#{i}=", v) }
      k.service(*a)
    end

    # Injects a middleware:
    #
    #   module Blog
    #     use Rack::MethodOverride
    #     use Rack::Session::Memcache, :key => "session"
    #   end
    def use(*a, &b)
      m = a.shift.new(method(:call), *a, &b)
      meta_def(:call) { |e| m.call(e) }
    end

    # A hash where you can set different settings.
    def options
      O
    end

    # Shortcut for setting options:
    #
    #   module Blog
    #     set :secret, "Hello!"
    #   end
    def set(k, v)
      O[k] = v
    end
  end

  # Views is an empty module for storing methods which create HTML. The HTML
  # is described using the Markaby language.
  #
  # == Defining and calling templates
  #
  # Templates are simply Ruby methods with Markaby inside:
  #
  #   module Blog::Views
  #     def index
  #       p "Welcome to my blog"
  #     end
  #
  #     def show
  #       h1 @post.title
  #       self << @post.content
  #     end
  #   end
  #
  # In your controllers you just call <tt>render :template_name</tt>
which will
  # invoke the template. The views and controllers will share instance
  # variables (as you can see above).
  #
  # == Using the layout method
  #
  # If your Views module has a <tt>layout</tt> method defined, it will be
  # called with a block which will insert content from your view:
  #
  #   module Blog::Views
  #     def layout
  #       html do
  #         head { title "My Blog "}
  #         body { self << yield }
  #       end
  #     end
  #   end
  module Views; include X, Helpers end

  # Models is an empty Ruby module for housing model classes derived
  # from ActiveRecord::Base. As a shortcut, you may derive from Base
  # which is an alias for ActiveRecord::Base.
  #
  #   module Camping::Models
  #     class Post < Base; belongs_to :user end
  #     class User < Base; has_many :posts end
  #   end
  #
  # == Where Models are Used
  #
  # Models are used in your controller classes. However, if your model class
  # name conflicts with a controller class name, you will need to refer
to it
  # using the Models module.
  #
  #   module Camping::Controllers
  #     class Post < R '/post/(\d+)'
  #       def get(post_id)
  #         @post = Models::Post.find post_id
  #         render :index
  #       end
  #     end
  #   end
  #
  # Models cannot be referred from Views at this time.
  module Models
    autoload :Base,'camping/ar'
    Helpers.send(:include, X, self)
  end

  autoload :Mab, 'camping/mab'
  autoload :Template, 'camping/template'
  C
end


_______________________________________________
Camping-list mailing list
Camping-list@rubyforge.org
http://rubyforge.org/mailman/listinfo/camping-list
  • camping.rb -- so you know what gmane.comp.lang.ruby.camping.... GM Rubinstein
    • src GM Rubinstein

Reply via email to