From e1992e3dba3ca2ce5af750418fd355653d587158 Mon Sep 17 00:00:00 2001 From: Colin Mitchell Date: Thu, 8 Dec 2011 21:42:01 -0500 Subject: [PATCH] initial commit --- .gitignore | 4 ++ .rvmrc | 1 + Gemfile | 16 +++++ Rakefile | 1 + gopher.gemspec | 27 +++++++++ lib/ext/object.rb | 17 ++++++ lib/ext/string.rb | 20 +++++++ lib/gopher.rb | 39 +++++++++++++ lib/gopher/configuration.rb | 21 +++++++ lib/gopher/connection.rb | 31 ++++++++++ lib/gopher/dispatching.rb | 21 +++++++ lib/gopher/errors.rb | 12 ++++ lib/gopher/gophlet.rb | 46 +++++++++++++++ lib/gopher/helpers.rb | 7 +++ lib/gopher/rendering.rb | 109 +++++++++++++++++++++++++++++++++++ lib/gopher/routing.rb | 59 +++++++++++++++++++ lib/gopher/server.rb | 36 ++++++++++++ lib/gopher/templating.rb | 69 ++++++++++++++++++++++ lib/gopher/utils.rb | 32 ++++++++++ lib/gopher/version.rb | 3 + lib/gophlets/file_browser.rb | 36 ++++++++++++ spec/configuration_spec.rb | 23 ++++++++ spec/ext_spec.rb | 24 ++++++++ spec/file_browser_spec.rb | 22 +++++++ spec/gopher_spec.rb | 1 + spec/gophlet_spec.rb | 43 ++++++++++++++ spec/helpers_spec.rb | 16 +++++ spec/rendering_spec.rb | 24 ++++++++ spec/routing_spec.rb | 45 +++++++++++++++ spec/sandbox/old/socks.txt | 0 spec/sandbox/socks.txt | 0 spec/server_spec.rb | 48 +++++++++++++++ spec/spec_helper.rb | 15 +++++ spec/templating_spec.rb | 23 ++++++++ spec/utils_spec.rb | 16 +++++ specs.watchr | 60 +++++++++++++++++++ 36 files changed, 967 insertions(+) create mode 100644 .gitignore create mode 100644 .rvmrc create mode 100644 Gemfile create mode 100644 Rakefile create mode 100644 gopher.gemspec create mode 100644 lib/ext/object.rb create mode 100644 lib/ext/string.rb create mode 100644 lib/gopher.rb create mode 100644 lib/gopher/configuration.rb create mode 100644 lib/gopher/connection.rb create mode 100644 lib/gopher/dispatching.rb create mode 100644 lib/gopher/errors.rb create mode 100644 lib/gopher/gophlet.rb create mode 100644 lib/gopher/helpers.rb create mode 100644 lib/gopher/rendering.rb create mode 100644 lib/gopher/routing.rb create mode 100644 lib/gopher/server.rb create mode 100644 lib/gopher/templating.rb create mode 100644 lib/gopher/utils.rb create mode 100644 lib/gopher/version.rb create mode 100644 lib/gophlets/file_browser.rb create mode 100644 spec/configuration_spec.rb create mode 100644 spec/ext_spec.rb create mode 100644 spec/file_browser_spec.rb create mode 100644 spec/gopher_spec.rb create mode 100644 spec/gophlet_spec.rb create mode 100644 spec/helpers_spec.rb create mode 100644 spec/rendering_spec.rb create mode 100644 spec/routing_spec.rb create mode 100644 spec/sandbox/old/socks.txt create mode 100644 spec/sandbox/socks.txt create mode 100644 spec/server_spec.rb create mode 100644 spec/spec_helper.rb create mode 100644 spec/templating_spec.rb create mode 100644 spec/utils_spec.rb create mode 100644 specs.watchr diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4040c6c --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.gem +.bundle +Gemfile.lock +pkg/* diff --git a/.rvmrc b/.rvmrc new file mode 100644 index 0000000..d1739b8 --- /dev/null +++ b/.rvmrc @@ -0,0 +1 @@ +rvm use 1.9.2-head@gopherpedia diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..fd413cb --- /dev/null +++ b/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 diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..2995527 --- /dev/null +++ b/Rakefile @@ -0,0 +1 @@ +require "bundler/gem_tasks" diff --git a/gopher.gemspec b/gopher.gemspec new file mode 100644 index 0000000..ab98836 --- /dev/null +++ b/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 diff --git a/lib/ext/object.rb b/lib/ext/object.rb new file mode 100644 index 0000000..cf28118 --- /dev/null +++ b/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 diff --git a/lib/ext/string.rb b/lib/ext/string.rb new file mode 100644 index 0000000..f530432 --- /dev/null +++ b/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 diff --git a/lib/gopher.rb b/lib/gopher.rb new file mode 100644 index 0000000..9be93fe --- /dev/null +++ b/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 diff --git a/lib/gopher/configuration.rb b/lib/gopher/configuration.rb new file mode 100644 index 0000000..860c251 --- /dev/null +++ b/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 diff --git a/lib/gopher/connection.rb b/lib/gopher/connection.rb new file mode 100644 index 0000000..b48a450 --- /dev/null +++ b/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 diff --git a/lib/gopher/dispatching.rb b/lib/gopher/dispatching.rb new file mode 100644 index 0000000..b7e70e9 --- /dev/null +++ b/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 diff --git a/lib/gopher/errors.rb b/lib/gopher/errors.rb new file mode 100644 index 0000000..ce0844c --- /dev/null +++ b/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 diff --git a/lib/gopher/gophlet.rb b/lib/gopher/gophlet.rb new file mode 100644 index 0000000..b5bf56e --- /dev/null +++ b/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 diff --git a/lib/gopher/helpers.rb b/lib/gopher/helpers.rb new file mode 100644 index 0000000..bfa235d --- /dev/null +++ b/lib/gopher/helpers.rb @@ -0,0 +1,7 @@ +module Gopher + module Helpers + def helpers(&block) + Gopher::Rendering::RenderContext.class_eval(&block) + end + end +end \ No newline at end of file diff --git a/lib/gopher/rendering.rb b/lib/gopher/rendering.rb new file mode 100644 index 0000000..9c14f8e --- /dev/null +++ b/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 diff --git a/lib/gopher/routing.rb b/lib/gopher/routing.rb new file mode 100644 index 0000000..3799280 --- /dev/null +++ b/lib/gopher/routing.rb @@ -0,0 +1,59 @@ +module Gopher + # The routing module. Sanitizes selectors, routes requests and does awesome shit + module Routing + # Add a shortcut to the route map to instances of +klass+ + def self.extended(klass) + klass.class_eval { def router; self.class.router.with(self); end } + end + + # The route map for this server / gophlet + def router + @router ||= RouteMap.new(self) + end + + # Define the routes for this server / gophlet + def routing(&block) + router.instance_eval(&block) + end + + # The route map itself + class RouteMap # nodoc + attr_accessor :routes, :owner + + # Route +selector+ to +gophlet+ or define behaviour inline through +block+ + def route(raw, gophlet = nil, *args, &block) + selector = Gopher::Utils.sanitize_selector(raw) + + if gophlet # Route to an gophlet +puts gophlet.expected_arguments + matcher = %r{^\/?#{selector}(\/?#{gophlet.expected_arguments})$} +puts matcher.inspect + routes[matcher] = gophlet.new(raw, *args) + elsif block_given? # Create an InlineGophlet for this route + matcher = %r{^\/?#{selector}$} + routes[matcher] = InlineGophlet.new(owner, &block) + else + raise ArgumentError.new('Route to an gophlet or define behaviour inline through a block') + end + end + + def initialize(gophlet) # nodoc + @owner = gophlet + @routes = {} + end + + def with(gophlet) # nodoc + @owner = gophlet; self + end + + # Grab the gophlet tied to +selector+ in the route map + def lookup(raw) + selector = Gopher::Utils.sanitize_selector(raw) + routes.find do |k, v| + return v, *$~[1..-1] if k=~ selector + end + raise NotFound + end + end + end +end diff --git a/lib/gopher/server.rb b/lib/gopher/server.rb new file mode 100644 index 0000000..a8d0fe1 --- /dev/null +++ b/lib/gopher/server.rb @@ -0,0 +1,36 @@ +module Gopher + class Server + extend Configuration + extend Routing + extend Templating # mmm + extend Dispatching + extend Helpers + + def self.host; config[:host] || '0.0.0.0' end # Shortcut for getting the server host + def self.port; config[:port] || 70 end # Shortcut for getting the server port + + def self.reload! + if config[:reload].class == String + config[:reload] = [ config[:reload] ] + end + + + config[:reload].each do |f| + load f if File.mtime(f) > @last_reload + @last_reload = Time.now + end + end + + def self.run + return if ::EM.reactor_running? + trap("INT") { exit } + + ::EM.run do + @last_reload = Time.now + ::EM.start_server(host, port, Gopher::Connection) do |c| + reload! if config[:reload] + end + end + end + end +end diff --git a/lib/gopher/templating.rb b/lib/gopher/templating.rb new file mode 100644 index 0000000..672d4c1 --- /dev/null +++ b/lib/gopher/templating.rb @@ -0,0 +1,69 @@ +module Gopher + module Templating + # Defines the dsl for creating new templates + # All templates defined here are added to +klass+#templates + class TemplateContext + attr_accessor :klass + + def initialize(klass, &block) + @klass = klass + instance_eval(&block) + end + + # Define a menu template. The contents of this template will be rendered in a MenuContext + def menu(name, &block) + context = Gopher::Rendering::MenuContext + @klass.templates[name] = block, context + end + + # Define a new basic text template + def template(name, &block) + context = Gopher::Rendering::TextContext + @klass.templates[name] = block, context + end + + # Define a partial template. Gets run in the same context it is called in + def partial(name, &block) + @klass.partials[name] = block + end + end + + # Search the local "template space" for a match + # If nothing is found, move on to the global templates + def find_template(template) + local = templates[template] + local || find_global_template(template) + end + + # Search the local "partials space" for a match + # Does not move on to the global space (no global partials) + def find_partial(partial) + partial = partials[partial] + partial || @host.find_partial(partial) + end + + # Search the server's global templates for a match + def find_global_template(template) + global = Gopher::Server.templates[template] + global || raise(TemplateNotFound) + end + + # A hash of all templates registered to this gophlet + # +block+ is evaluated in a TemplateContext to create new templates + def templates(&block) + @templates ||= {} + block_given? ? TemplateContext.new(self, &block) : @templates + end + + # All partials registered to this gophlet + def partials + @partials ||= {} + end + + # Add a shortcut to the class finder to instances of +klass+ + def self.extended(klass) + klass.class_eval { def find_template(t); self.class.find_template(t); end } + klass.class_eval { def find_partial(p); self.class.find_partial(p); end } + end + end +end diff --git a/lib/gopher/utils.rb b/lib/gopher/utils.rb new file mode 100644 index 0000000..fd41870 --- /dev/null +++ b/lib/gopher/utils.rb @@ -0,0 +1,32 @@ +module Gopher + module Utils + # Sanitizes a gopher selector + def self.sanitize_selector(raw) + selector = raw.to_s.dup + selector.strip! # Strip whitespace + selector.sub!(/\/$/, '') # Strip last rslash + selector.sub!(/^\/*/, '/') # Strip extra lslashes + selector.gsub!(/\.+/, '.') # Don't want consecutive dots! + selector + end + + # Sanitizes text for use in gopher menus + def self.sanitize_text(raw) + text = raw.dup + text.rstrip! # Remove excess whitespace + text.gsub!(/\t/, ' ' * 8) # Tabs to spaces + text.gsub!(/\n/, '') # Get rid of newlines (\r as well?) + text + end + + # Determines the gopher type for +selector+ based on the extension + def self.determine_type(selector) + ext = File.extname(selector).downcase + case ext + when '.jpg', '.png' then 'I' + when '.mp3', '.wav' then 's' + else '0' + end + end + end +end diff --git a/lib/gopher/version.rb b/lib/gopher/version.rb new file mode 100644 index 0000000..3f4c856 --- /dev/null +++ b/lib/gopher/version.rb @@ -0,0 +1,3 @@ +module Gopher + VERSION = "0.0.1" +end diff --git a/lib/gophlets/file_browser.rb b/lib/gophlets/file_browser.rb new file mode 100644 index 0000000..029a7db --- /dev/null +++ b/lib/gophlets/file_browser.rb @@ -0,0 +1,36 @@ +module Gopher + class FileBrowser < Gopher::Gophlet + attr_accessor :root + + def initialize(base, root) + raise Errno::ENOENT unless File.directory?(root) + @root = File.expand_path(root) + super(base) + end + + def dispatch(selector) + path = File.join(root, selector) + if File.directory? path + render :directory, base, path # check for index? + elsif File.file? path + return File.open(path) + else + raise NotFound, 'File or directory not found' + end + end + + templates do + menu :directory do |base, dir| + Dir["#{dir}/*"].each do |path| + basename = File.basename(path) + if File.directory? path + menu basename, File.join(base, basename) + else + link basename, File.join(base, basename) + end + end + text "---" + end + end + end +end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb new file mode 100644 index 0000000..4c92909 --- /dev/null +++ b/spec/configuration_spec.rb @@ -0,0 +1,23 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Configuration do + before(:all) do + @app = Object.new + @app.extend(Gopher::Configuration) + end + + it 'should start out empty' do + @app.config.should be_empty + end + + it 'should let us do configuration through a block' do + @app.config { title 'a title' } + @app.config[:title].should == 'a title' + end + + it 'should not confuse separate configurations' do + obj = Object.new + obj.extend(Gopher::Configuration) + obj.config.should be_empty + end +end diff --git a/spec/ext_spec.rb b/spec/ext_spec.rb new file mode 100644 index 0000000..faceb61 --- /dev/null +++ b/spec/ext_spec.rb @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# require File.join(File.dirname(__FILE__), '/spec_helper') + +# describe String, '#wrap' do +# it 'should wrap ab cd e to ab' do +# "ab cd e".wrap(2).should == "ab\ncd\ne" +# end + +# it 'should not wrap lines with no spaces' do +# "abcde".wrap(2).should == "abcde" +# end + +# it 'should yield wrapped lines when called with a block' do +# array = [] +# "ab cd efg".wrap(2) do |line| +# array << line +# end +# array.should == ["ab", "cd", "efg"] +# end + +# it 'should handle unicode strings' do +# "å å".wrap(3).should == "å å" +# end +# end diff --git a/spec/file_browser_spec.rb b/spec/file_browser_spec.rb new file mode 100644 index 0000000..0b969f1 --- /dev/null +++ b/spec/file_browser_spec.rb @@ -0,0 +1,22 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::FileBrowser do + before(:all) do + path = File.join(File.dirname(__FILE__), 'sandbox') + @gophlet = Gopher::FileBrowser.new('/files', path) + end + + it 'should dispatch to files' do + @gophlet.dispatch('/socks.txt').should be_instance_of(File) + end + + it 'should do directory listings' do + str = "1old\t/files/old\t0.0.0.0\t70\r\n0socks.txt\t/files/socks.txt\t0.0.0.0\t70\r\ni---\tnull\t(FALSE)\t0\r\n" + @gophlet.dispatch('/').should == str + end + + it 'should do directory listings' do + str = "0socks.txt\t/files/socks.txt\t0.0.0.0\t70\r\ni---\tnull\t(FALSE)\t0\r\n" + @gophlet.dispatch('/old').should == str + end +end diff --git a/spec/gopher_spec.rb b/spec/gopher_spec.rb new file mode 100644 index 0000000..28565ac --- /dev/null +++ b/spec/gopher_spec.rb @@ -0,0 +1 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') diff --git a/spec/gophlet_spec.rb b/spec/gophlet_spec.rb new file mode 100644 index 0000000..32b111d --- /dev/null +++ b/spec/gophlet_spec.rb @@ -0,0 +1,43 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Gophlet do + before(:all) do + class Phlog < Gopher::Gophlet + extend Gopher::Routing + + routing do + route '/' do + render :index + end + + route '/foo/(.*)' do |foo| + render :echo, foo + end + end + + templates do + menu :index do + text "Oh yeah" + end + + menu :echo do |*a| + text *a + end + end + end + + @phlog = Phlog.new + end + + it 'should add templates' do + Phlog.templates.should include(:index) + end + + it 'should accept dispatches' do + @phlog.dispatch('/').should == "iOh yeah\tnull\t(FALSE)\t0\r\n" + end + + it 'should accept dispatches' do + @phlog.dispatch('/foo/zomg').should == "izomg\tnull\t(FALSE)\t0\r\n" + end +end diff --git a/spec/helpers_spec.rb b/spec/helpers_spec.rb new file mode 100644 index 0000000..f27ab66 --- /dev/null +++ b/spec/helpers_spec.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Helpers do + before(:all) do + obj = Object.new + obj.extend(Gopher::Helpers) + + obj.helpers do + def foo; end + end + end + + it 'should add code to RenderContext' do + Gopher::Rendering::RenderContext.public_instance_methods.should include('foo') + end +end diff --git a/spec/rendering_spec.rb b/spec/rendering_spec.rb new file mode 100644 index 0000000..29046e1 --- /dev/null +++ b/spec/rendering_spec.rb @@ -0,0 +1,24 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Rendering::RenderContext do + before(:all) do + @ctx = Gopher::Rendering::RenderContext.new + end + + it 'should add text' do + @ctx.text("cake") + @ctx.text("oh yes") + @ctx.result.should == "cakeoh yes" + end +end + +describe Gopher::Rendering::MenuContext do + before(:all) do + @ctx = Gopher::Rendering::MenuContext.new + end + + it 'should add text as a gopher line' do + @ctx.text("tortilla") + @ctx.result.should == "itortilla\tnull\t(FALSE)\t0\r\n" + end +end diff --git a/spec/routing_spec.rb b/spec/routing_spec.rb new file mode 100644 index 0000000..041d68b --- /dev/null +++ b/spec/routing_spec.rb @@ -0,0 +1,45 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Routing, 'with a mock gophlet' do + before(:all) do + class OuterGophlet < Gopher::Gophlet; end + + class InnerGophlet < Gopher::Gophlet + def self.expected_arguments; /[a-z]*/ end # For most gophlets, this will simply be /\/.*/ + end + + @app = OuterGophlet + @app.extend(Gopher::Routing) + + @app.routing do + route '/foo', InnerGophlet + + route '/' do + return '-' + end + end + end + + it 'should route /foo to the mock gophlet' do + gophlet, args = @app.router.lookup('/foo') + + gophlet.should be_instance_of(InnerGophlet) + args.should == '' + end + + it 'should route /foo/excellent to the mock gophlet' do + gophlet, args = @app.router.lookup('/foo/excellent') + + gophlet.should be_instance_of(InnerGophlet) + args.should == '/excellent' + end + + it 'should not route non-matches to the gophlet' do + proc { @app.router.lookup('/foo/123') }.should raise_error(Gopher::NotFound) + proc { @app.router.lookup('/foo//excellent') }.should raise_error(Gopher::NotFound) + end + + it 'should route to gophlets defined inline' do + @app.router.lookup("/").should_not be_empty + end +end diff --git a/spec/sandbox/old/socks.txt b/spec/sandbox/old/socks.txt new file mode 100644 index 0000000..e69de29 diff --git a/spec/sandbox/socks.txt b/spec/sandbox/socks.txt new file mode 100644 index 0000000..e69de29 diff --git a/spec/server_spec.rb b/spec/server_spec.rb new file mode 100644 index 0000000..2f3cdcc --- /dev/null +++ b/spec/server_spec.rb @@ -0,0 +1,48 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Server do + before(:all) do + + # Configure + Gopher.server do + + # Configure the main server + config do + host 'pha.hk' + port 70 + end + + routing do + route '/' do + render :index + end # How about a shortcut for this? route '/', :index ? + end + + # Add some global templates + templates do + menu :index do + text "pha.hk gopherspace" + end + end + end + end + + it 'should set the host and port' do + Gopher::Server.host.should == 'pha.hk' + Gopher::Server.port.should == 70 + end + + it 'should add the index template' do + Gopher::Server.templates[:index].should_not be_empty + end + + it 'should add the inline index gophlet to routes' do + gophlet, args = Gopher::Server.router.lookup('/') + gophlet.should be_instance_of(Gopher::InlineGophlet) + end + + it 'should dispatch to the inline index gophlet' do + result = Gopher::Server.dispatch('/') + result.should == "ipha.hk gopherspace\tnull\t(FALSE)\t0\r\n" + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b52b253 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,15 @@ +#require 'simplecov' +#SimpleCov.start + +#$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +#$LOAD_PATH.unshift(File.dirname(__FILE__)) + +# Grab the gophlet tied to +selector+ in the route map + + +require 'bundler/setup' +Bundler.require + +# Requires supporting files with custom matchers and macros, etc, +# in ./support/ and its subdirectories. +Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f} diff --git a/spec/templating_spec.rb b/spec/templating_spec.rb new file mode 100644 index 0000000..257cfc4 --- /dev/null +++ b/spec/templating_spec.rb @@ -0,0 +1,23 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Templating do + before(:all) do + @klass = Class.new + @klass.extend(Gopher::Templating) + + @klass.templates do + menu :index do + "foo" + end + end + @obj = @klass.new + end + + it 'should store templates' do + @klass.templates.should include(:index) + end + + it 'should find templates' do + @obj.find_template(:index).should_not be_empty + end +end diff --git a/spec/utils_spec.rb b/spec/utils_spec.rb new file mode 100644 index 0000000..84aac93 --- /dev/null +++ b/spec/utils_spec.rb @@ -0,0 +1,16 @@ +require File.join(File.dirname(__FILE__), '/spec_helper') + +describe Gopher::Utils do + it 'should sanitize selectors' do + raw = '//.../...heyho' + sane = Gopher::Utils.sanitize_selector(raw) + sane.should == '/./.heyho' + end + + it 'should determine gopher types from filenames' do + Gopher::Utils.determine_type("hey.jpg").should == 'I' + Gopher::Utils.determine_type("hey.PNG").should == 'I' + Gopher::Utils.determine_type("hey.wav").should == 's' + Gopher::Utils.determine_type("hey.lulz").should == '0' + end +end diff --git a/specs.watchr b/specs.watchr new file mode 100644 index 0000000..84becda --- /dev/null +++ b/specs.watchr @@ -0,0 +1,60 @@ +# Run me with: +# +# $ watchr specs.watchr + +# -------------------------------------------------- +# Convenience Methods +# -------------------------------------------------- +def all_spec_files + Dir['spec/**/*_spec.rb'] +end + +def run_spec_matching(thing_to_match) + matches = all_spec_files.grep(/#{thing_to_match}/i) + if matches.empty? + puts "Sorry, thanks for playing, but there were no matches for #{thing_to_match}" + else + run matches.join(' ') + end +end + +def run(files_to_run) + puts("Running: #{files_to_run}") + system("clear;rspec -cfs #{files_to_run}") + no_int_for_you +end + +def run_all_specs + run(all_spec_files.join(' ')) +end + +# -------------------------------------------------- +# Watchr Rules +# -------------------------------------------------- +watch('^spec/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) } +#watch('^spec/controllers/(.*)_spec\.rb') { |m| run_spec_matching(m[1]) } + +watch("^lib/chatterbot/(.*)\.rb") { |m| run_spec_matching(m[1]) } +#watch('^spec/spec_helper\.rb') { run_all_specs } + +# -------------------------------------------------- +# Signal Handling +# -------------------------------------------------- + +def no_int_for_you + @sent_an_int = nil +end + +Signal.trap 'INT' do + if @sent_an_int then + puts " A second INT? Ok, I get the message. Shutting down now." + exit + else + puts " Did you just send me an INT? Ugh. I'll quit for real if you do it again." + @sent_an_int = true + Kernel.sleep 1.5 + run_all_specs + end +end + +run_all_specs