Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
muffinista committed Dec 9, 2011
0 parents commit e1992e3
Show file tree
Hide file tree
Showing 36 changed files with 967 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
@@ -0,0 +1,4 @@
*.gem
.bundle
Gemfile.lock
pkg/*
1 change: 1 addition & 0 deletions .rvmrc
@@ -0,0 +1 @@
rvm use 1.9.2-head@gopherpedia
16 changes: 16 additions & 0 deletions Gemfile
@@ -0,0 +1,16 @@
source "http://rubygems.org"

# Specify your gem's dependencies in gopher.gemspec
gemspec

# Add dependencies to develop your gem here.
# Include everything needed to run rake, tests, features, etc.
group :development do
gem 'simplecov', :require => false, :group => :test

gem "shoulda", ">= 0"
gem "rspec"

gem "bundler", "~> 1.0.0"
gem "watchr"
end
1 change: 1 addition & 0 deletions Rakefile
@@ -0,0 +1 @@
require "bundler/gem_tasks"
27 changes: 27 additions & 0 deletions gopher.gemspec
@@ -0,0 +1,27 @@
# -*- encoding: utf-8 -*-
$:.push File.expand_path("../lib", __FILE__)
require "gopher/version"

Gem::Specification.new do |s|
s.name = "gopher"
s.version = Gopher::VERSION
s.authors = ["Colin Mitchell"]
s.email = ["colin@muffinlabs.com"]
s.homepage = "http://muffinlabs.com"
s.summary = %q{TODO: Write a gem summary}
s.description = %q{TODO: Write a gem description}

s.rubyforge_project = "gopher"

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"]

# specify any dependencies here; for example:
s.add_development_dependency "rspec"
s.add_runtime_dependency "eventmachine"
s.add_runtime_dependency "logger"

# s.add_runtime_dependency "rest-client"
end
17 changes: 17 additions & 0 deletions lib/ext/object.rb
@@ -0,0 +1,17 @@
# instance_exec for 1.8
# Grabbed from Mauricio Fernandez (thanks!)
# For more information, see http://eigenclass.org/hiki.rb?instance_exec
class Object
module InstanceExecHelper; end
include InstanceExecHelper
def instance_exec(*args, &block) # !> method redefined; discarding old instance_exec
mname = "__instance_exec_#{Thread.current.object_id.abs}_#{object_id.abs}"
InstanceExecHelper.module_eval{ define_method(mname, &block) }
begin
ret = send(mname, *args)
ensure
InstanceExecHelper.module_eval{ remove_method(mname) } rescue nil
end
ret
end
end
20 changes: 20 additions & 0 deletions lib/ext/string.rb
@@ -0,0 +1,20 @@
class String
# Wraps a string to +width+ characters and returns the text with newlines inserted
# If +block+ is given, yields each line for you to play around with
def wrap(width=80, &block)
text = self.dup
p = text.rindex(' ', width) # Position of the last space
wrap = text.jlength > width && p # Do we wrap?
if block_given?
if wrap # Only wrap if the text is long enough and has spaces
yield text[0,p]
text[p+1..-1].wrap(width, &block)
else
yield text
end
else
return text unless wrap
"#{text[0,p]}\n#{text[p+1..-1].wrap(width)}"
end
end
end
39 changes: 39 additions & 0 deletions lib/gopher.rb
@@ -0,0 +1,39 @@
require "gopher/version"

require 'ext/object'
require 'ext/string'

#require 'jcode'
require 'logger'
require 'eventmachine'
require 'stringio'

require 'gopher/utils'
require 'gopher/errors'
require 'gopher/configuration'
require 'gopher/routing'

require 'gopher/templating'
require 'gopher/rendering'
require 'gopher/dispatching'

require 'gopher/helpers'

require 'gopher/server'
require 'gopher/gophlet'

require 'gopher/connection'

require 'gophlets/file_browser'

module Gopher

# Defines the Gopher server through the +block+
def self.server(&block)
Gopher::Server.instance_eval(&block) if block_given?
end

def self.logger
@logger ||= Logger.new('gopher.log')
end
end
21 changes: 21 additions & 0 deletions lib/gopher/configuration.rb
@@ -0,0 +1,21 @@
module Gopher
module Configuration
def config(&block)
@configuration ||= {}
block_given? ? ConfigContext.new(self, &block) : @configuration
end

# Neat idea from merb!
class ConfigContext # nodoc
def initialize(klass, &block) #:nodoc:
@klass = klass
instance_eval(&block)
end

def method_missing(method, *args) #:nodoc:
#@klass.config[method] = *args
@klass.config[method] = args.first
end
end
end
end
31 changes: 31 additions & 0 deletions lib/gopher/connection.rb
@@ -0,0 +1,31 @@
module Gopher
module Connection
def receive_data(selector)
begin
raise InvalidRequest, "Message too long" if selector.length >= 255

Gopher.logger.info "Dispatching to #{selector}"

response = Gopher::Server.dispatch(selector)

case response
when String then send_data(response)
when StringIO then send_data(response.read)
when File
while chunk = response.read(8192) do
send_data(chunk)
end
end
rescue Gopher::NotFound => e
Gopher.logger.error "Unknown selector. #{e}"
rescue Gopher::InvalidRequest => e
Gopher.logger.error "Invalid request. #{e}"
rescue => e
Gopher.logger.error "Bad juju afoot. #{e}"; puts e
raise e
ensure
close_connection_after_writing
end
end
end
end
21 changes: 21 additions & 0 deletions lib/gopher/dispatching.rb
@@ -0,0 +1,21 @@
module Gopher
module Dispatching
def dispatch(raw)

puts raw

raw_selector, raw_input = raw.split("\t")
selector = Gopher::Utils.sanitize_selector(raw_selector)
receiver, *arguments = router.lookup(selector)
puts "LOOKUP: #{receiver} #{arguments.inspect}"

puts receiver

case receiver
when Gopher::InlineGophlet then receiver.with(router.owner, raw_input).call(*arguments)
else
receiver.dispatch("#{arguments}\t#{raw_input}")
end
end
end
end
12 changes: 12 additions & 0 deletions lib/gopher/errors.rb
@@ -0,0 +1,12 @@
module Gopher
class GopherError < StandardError; end

# When a selector isn't found in the route map
class NotFound < GopherError; end

# Invalid gopher requests
class InvalidRequest < GopherError; end

# Template not found in local or global space
class TemplateNotFound < GopherError; end
end
46 changes: 46 additions & 0 deletions lib/gopher/gophlet.rb
@@ -0,0 +1,46 @@
module Gopher
# A basic gophlet.
#
# You can subclass this to get some behaviour for free in your own stand-alone gophlets,
# such as helpers, templates, rendering and...uh...
class Gophlet
attr_accessor :base

def self.expected_arguments
config[:moo] || /.*/
end

extend Templating
extend Configuration
extend Helpers
extend Routing

include Dispatching
include Rendering

def initialize(base='/')
@base = base
end
end

# Inline gophlets are instances of InlineGophlet
class InlineGophlet < Gophlet
attr_accessor :host, :block, :input

def initialize(host, &block)
@block = block
@host = host
end

def with(host, input)
@host, @input = host, input.to_s.strip; self
end

def call(*arguments)
self.instance_exec(*arguments, &block)
end

def find_template(template); host.find_template(template) end
def find_partial(partial); host.find_partial(partial) end
end
end
7 changes: 7 additions & 0 deletions lib/gopher/helpers.rb
@@ -0,0 +1,7 @@
module Gopher
module Helpers
def helpers(&block)
Gopher::Rendering::RenderContext.class_eval(&block)
end
end
end
109 changes: 109 additions & 0 deletions lib/gopher/rendering.rb
@@ -0,0 +1,109 @@
module Gopher
module Rendering
# All rendering of templates (inline and otherwise) is done inside a RenderContext
class RenderContext
attr_accessor :result

def initialize(host=nil) # nodoc
@_host = host
@result = ""
end

def <<(string); @result << string.to_s; end

# Adds +text+ to the result
def text(text); self << text; end

# Adds +n+ empty lines
def br(n=1); n.times { text '' } end

# Wraps +text+ to +width+ characters
def block(text, width=80)
text.each_line do |line|
line.wrap(width) { |chunk| text chunk.rstrip }
end
end

# Experimental! No specs! Ayeee!
def partial(partial, *args)
args.flatten!
partial = @_host.find_partial(partial)
if args.empty?
self.instance_exec(*args, &partial)
else
args.each do |a|
self.instance_exec(a, &partial)
end
end
end

def url(selector)
_host ? "#{_host.base}#{selector}" : selector
end

private
def _host
@_host.host rescue nil
end
end

# Render text files
class TextContext < RenderContext
def link(txt, *args)
text "#{txt}"
end

def menu(txt, *args)
text "#{txt}"
end

def search(*args); end
alias input search

def text(text)
self << text
self << "\n"
end
end

# The MenuContext is for rendering gopher menus
class MenuContext < RenderContext
# Creates a gopher menu line from +type+, +text+, +selector+, +host+ and +port+
# +host+ and +post+ will default to the current host and port of the running Gopher server
# (by default 0.0.0.0 and 70)
# +text+ will be sanitized according to a few simple rules (see Gopher::Utils)
def line(type, text, selector, host=Gopher::Server.host, port=Gopher::Server.port)
text = Gopher::Utils.sanitize_text(text)

self << ["#{type}#{text}", selector, host, port].join("\t")
self << "\r\n" # End the gopher line
end

def text(text)
line 'i', text, 'null', '(FALSE)', 0
end

def link(text, selector, *args)
type = Gopher::Utils.determine_type(selector)
line type, text, selector, *args
end

def search(text, selector, *args)
line '7', text, selector, *args
end
alias input search

def menu(text, selector, *args)
line '1', text, selector, *args
end
end

# Find the right template (with context) and instance_exec it inside the context
def render(template, *arguments)
block, context = find_template(template)
ctx = context.new(self)
ctx.instance_exec(*arguments, &block)
ctx.result
end
end
end

0 comments on commit e1992e3

Please sign in to comment.