Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

. imports code from another repository

This was developed as a tool to a project, now I'd like to publish this
as a gem.
  • Loading branch information...
commit 941f648c330561403758e56a814192087c92ef1d 1 parent 3784fdf
@kschiess authored
View
5 History.txt
@@ -1,6 +1,3 @@
=== 1.0.0 / 2009-07-16
-* 1 major enhancement
-
- * Birthday!
-
+* Releasing thin_ham
View
30 README.txt
@@ -1,26 +1,32 @@
= thin_ham
-* url
+Source:
+* http://github.com/kschiess/thin_ham
== DESCRIPTION:
-FIX (describe your package)
+thin_ham is a small tool to allow for fast prototyping of web sites. It will
+publish files written in haml & sass [1] or alternatively just publish
+whatever other files you happen to have lying around in the relevant
+directories.
-== FEATURES/PROBLEMS:
+Since it is written as a web server, it will allow running javascript in your
+prototype to use AJAX to load missing resources. This can be very useful when
+developing modern websites.
-* FIX (list of features or problems)
+Note that thin_ham should under NO circumstances be used as a public web
+server. It is not made that way.
-== SYNOPSIS:
-
- FIX (code sample of usage)
-== REQUIREMENTS:
-
-* FIX (list of requirements)
+== SYNOPSIS:
-== INSTALL:
+Move to a directory of your choice (where your prototype will be located) and
+run:
-* FIX (sudo gem install, anything else)
+ thin_ham
+
+thin_ham will then output an url under which the current directory can be
+accessed locally.
== LICENSE:
View
19 Rakefile
@@ -1,12 +1,19 @@
-# -*- ruby -*-
-
require 'rubygems'
require 'hoe'
-Hoe.spec 'thin_ham' do
- # developer('FIX', 'FIX@example.com')
+# Hoe.spec 'thin_ham' do
+# developer('Kaspar Schiess', 'kaspar.schiess@absurd.li')
+#
+# # self.rubyforge_name = 'thin_hamx' # if different than 'thin_ham'
+# end
+
+require 'rake'
+require 'spec/rake/spectask'
- # self.rubyforge_name = 'thin_hamx' # if different than 'thin_ham'
+desc "Run all examples"
+Spec::Rake::SpecTask.new('spec') do |t|
+ t.spec_files = FileList['spec/**/*.rb']
+ t.spec_opts = %w{--options spec/spec.opts}
end
-# vim: syntax=ruby
+task :default => :spec
View
7 bin/thin_ham
@@ -1,3 +1,8 @@
#!/usr/bin/env ruby
-abort "you need to write me"
+$:.unshift File.dirname(__FILE__) + "/../lib"
+require 'thin_ham/init'
+
+
+directory = ARGV.first || '.'
+ThinHam.start_server
View
3  lib/thin_ham.rb
@@ -1,3 +0,0 @@
-class ThinHam
- VERSION = '1.0.0'
-end
View
97 lib/thin_ham/hamlr.rb
@@ -0,0 +1,97 @@
+
+require 'time'
+require 'rack/mime'
+require 'rack/utils'
+
+require 'thin_ham/processors/markup'
+require 'thin_ham/processors/sass'
+require 'thin_ham/processors/haml'
+
+# Serves files from the docroot given in the constructor. If a file
+# ends with the file ending of a known processor (.haml or .sass), transform
+# the file and serve the result of the transformation.
+#
+# This is intended to be a prototyping helper and thus it performs no caching.
+#
+# CAUTION: Not for WAN exposed usage, not security reviewed !!! Sensible to
+# at least the '..'-kind of attack.
+#
+# CAUTION: All files are currently loaded into memory and then served. This
+# is probably not what you want to use for big movies.
+#
+# Based on rack/file.rb
+#
+class ThinHam::Hamlr
+ attr_reader :docroot
+ def initialize(docroot)
+ @docroot = docroot
+ end
+
+ def call(env)
+ dup.request(env)
+ end
+
+ # Handles request - this is thread-safe.
+ #
+ def request(env)
+ path_info = Rack::Utils.unescape(env["PATH_INFO"])
+ full_path = File.join(docroot, path_info)
+
+ processors.each do |processor|
+ mapped_path = processor.map_to_file(full_path)
+ if can_serve?(mapped_path)
+ return http_answer(
+ processor.serve(full_path),
+ mime_type(full_path))
+ end
+ end
+
+ return not_found(path_info)
+ end
+
+ # Returns a list of processors known to man
+ #
+ def processors
+ [
+ ThinHam::Processors::Haml.new,
+ ThinHam::Processors::Sass.new,
+ ThinHam::Processors::Markup.new # identity transform, serving files as files
+ ]
+ end
+
+ # Can we serve this file?
+ #
+ def can_serve?(file)
+ File.file?(file) && File.readable?(file)
+ end
+
+ # Returns the mime type (a guess!) for the given +filename+
+ #
+ def mime_type(filename)
+ Rack::Mime.mime_type(
+ File.extname(filename),
+ 'text/plain')
+ end
+
+ # Serves body given with mime_type and modif_date.
+ #
+ def http_answer(body, mime_type)
+ [200, {
+ 'Cache-Control' => 'max-age=0', # turns off browser cache (hopefully)
+ "Content-Type" => mime_type,
+ "Content-Length" => body.size.to_s
+ }, body]
+ end
+
+ # Returns a 404 not found answer.
+ #
+ def not_found(path)
+ body = "File not found: #{path}\n"
+ [404, {
+ 'Cache-Control' => 'max-age=3600',
+ "Content-Type" => "text/plain",
+ "Content-Length" => body.size.to_s
+ },
+ [body]]
+ end
+end
View
21 lib/thin_ham/init.rb
@@ -0,0 +1,21 @@
+
+require 'thin'
+
+module ThinHam
+ def start_server(directory='.')
+ puts "serving ham in thin slices at localhost:3000... (Ctrl+C to interrupt)"
+
+ Thin::Server.start('0.0.0.0', 3000) do
+ use Rack::CommonLogger
+
+ map '/' do
+ run ThinHam::Hamlr.new(directory)
+ end
+ end
+ end
+ module_function :start_server
+
+ module Processors; end
+end
+
+require 'thin_ham/hamlr'
View
43 lib/thin_ham/processors/haml.rb
@@ -0,0 +1,43 @@
+
+require 'haml'
+
+require 'thin_ham/processors/markup'
+
+class ThinHam::Processors::Haml < ThinHam::Processors::Markup
+
+ # Helper class for markup evaluation.
+ #
+ class Helper
+ attr_reader :base_path, :haml_processor
+ def initialize(base_path, haml_processor)
+ @base_path, @haml_processor = base_path, haml_processor
+ end
+
+ def include(file)
+ include_name = File.join(base_path, file)
+ haml_processor.serve(include_name)
+ end
+ end
+
+ # Serves the file by processing it and then calling #serve_with_file_origin
+ # on the controller.
+ #
+ def serve(file)
+ markup = read_markup(map_to_file(file))
+ base_path = File.dirname(file)
+
+ process(markup, base_path)
+ end
+
+ def map_to_file(path)
+ "#{path}.haml"
+ end
+
+ # Processes haml into html
+ #
+ def process(haml, base_path)
+ helper = Helper.new(base_path, self)
+
+ Haml::Engine.new(haml).render(helper)
+ end
+end
View
53 lib/thin_ham/processors/markup.rb
@@ -0,0 +1,53 @@
+
+
+# Generic markup processor that works by reading a file based on the
+# original filename, appending the markups extension.
+#
+# This class is abstract, implementors should override #map_to_file and
+# #process(markup), yielding target code. Using the Markup class itself
+# will give you an identity transform, meaning delivering files as themselves.
+#
+# Example:
+#
+# class FooMarkup < Markup
+# def map_to_file(filename)
+# filename + ".foo"
+# end
+# def process(foo)
+# foo.gsub('foo', 'bar')
+# end
+# end
+#
+class ThinHam::Processors::Markup
+ # Serves the file by processing it and then calling #serve_with_file_origin
+ # on the controller.
+ #
+ def serve(file)
+ markup = read_markup(map_to_file(file))
+
+ process(markup)
+ end
+
+ # Reads markup file contents
+ #
+ def read_markup(file)
+ File.read(file)
+ end
+
+ # Override these functions to implement a markup processor -----------------
+ # (NOTE: Default implementation doesn't do anything and will leave files
+ # unprocessed - this is interesting in its own right.)
+
+ # Maps a path in the filesystem below the document root to a file that
+ # contains markup - returning the new path of the markup file.
+ #
+ def map_to_file(path)
+ path
+ end
+
+ # Processes markup into the target code.
+ #
+ def process(markup)
+ markup
+ end
+end
View
16 lib/thin_ham/processors/sass.rb
@@ -0,0 +1,16 @@
+
+require 'sass'
+
+require 'thin_ham/processors/markup'
+
+class ThinHam::Processors::Sass < ThinHam::Processors::Markup
+ def map_to_file(path)
+ path + '.sass'
+ end
+
+ # Processes sass into css
+ #
+ def process(sass)
+ Sass::Engine.new(sass).render
+ end
+end
View
1  spec/fixtures/foo.html.haml
@@ -0,0 +1 @@
+= include 'pieces/bar'
View
1  spec/fixtures/html_file.html
@@ -0,0 +1 @@
+<html><body>This demonstrates a simple html file</body></html>
View
1  spec/fixtures/pieces/bar.haml
@@ -0,0 +1 @@
+bar
View
7 spec/fixtures/test1.css.sass
@@ -0,0 +1,7 @@
+body
+ :width 5000px
+ :background-color #3d4367
+ :font-family Helvetica
+ :font-size 14px
+ :line-height 1.2em
+ :margin 8px
View
4 spec/fixtures/test1.html.haml
@@ -0,0 +1,4 @@
+!!!
+!!! XML
+%body
+ = "This is a test"
View
1  spec/spec.opts
@@ -0,0 +1 @@
+--colour
View
23 spec/spec_helper.rb
@@ -0,0 +1,23 @@
+require 'spec'
+
+$:.unshift File.dirname(__FILE__) + "/../lib"
+
+require 'thin_ham/init'
+
+Spec::Runner.configure do |config|
+ config.mock_with :flexmock
+end
+
+FIXTURES_ROOT = File.join(
+ File.dirname(__FILE__),
+ '/fixtures')
+
+# Generates a full path from a path relative to the fixtures root
+#
+def fixture(path)
+ File.join(FIXTURES_ROOT, path)
+end
+
+def anything
+ FlexMock.any
+end
View
85 spec/unit/hamlr_spec.rb
@@ -0,0 +1,85 @@
+require File.dirname(__FILE__) + '/../spec_helper'
+
+require 'thin_ham/hamlr'
+
+require 'rack/mock'
+require 'rack/lint'
+
+describe ThinHam::Hamlr do
+ it "should be initialized with a path" do
+ ThinHam::Hamlr.new('.')
+ end
+
+ context 'fixtures (unit)' do
+ attr_reader :hamlr
+ before(:each) do
+ @hamlr = ThinHam::Hamlr.new(
+ FIXTURES_ROOT)
+ end
+
+ describe "#call" do
+ before(:each) do
+ # Don't duplicate instance - this makes mocking/stubbing possible
+ flexmock(hamlr).should_receive(:dup).and_return(hamlr)
+ end
+ end
+ describe "#http_answer" do
+ attr_reader :mtime_http, :result
+ before(:each) do
+ @result = hamlr.http_answer(
+ File.read(FIXTURES_ROOT + "/html_file.html"),
+ 'text/html')
+ end
+ it "should return http code 200 OK" do
+ result.first.should == 200
+ end
+ it "should have an header hash at second position" do
+ result.at(1).should be_an_instance_of(Hash)
+ end
+ it "should have a body at last position" do
+ result.last.size.should > 10
+ end
+
+ describe "header hash" do
+ attr_reader :headers
+ before(:each) do
+ @headers = result.at(1)
+ end
+ it { headers.should have_key('Content-Length') }
+ it { headers.should have_key('Content-Type') }
+ end
+ end
+ end
+ context 'fixtures (functional)' do
+ attr_reader :request
+ before(:each) do
+ @request = Rack::MockRequest.new(
+ Rack::Lint.new(
+ ThinHam::Hamlr.new(
+ FIXTURES_ROOT)))
+ end
+ it "should return a 404 if the file cannot be found" do
+ request.get('/this_file_doesnt_exist').should be_not_found
+ end
+ it "should return 200 OK for an html file" do
+ request.get('/html_file.html').should be_ok
+ end
+ it "should return the file content of html_file.html" do
+ request.get('/html_file.html').should match(/a simple html file/)
+ end
+
+ context 'haml rendering' do
+ it "should return 200 OK for test1.html" do
+ request.get('/test1.html').should be_ok
+ end
+ it "should contain html header for test1.html" do
+ request.get('/test1.html').should match(%r{<body>\s+This is a test\s+</body>})
+ end
+ end
+ context 'sass rendering' do
+ it "should return 200 OK for test1.css" do
+ request.get('/test1.css').should be_ok
+ end
+ end
+ end
+end
View
42 spec/unit/processors/haml_processor_spec.rb
@@ -0,0 +1,42 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe ThinHam::Processors::Haml do
+ attr_reader :processor
+ before(:each) do
+ @processor = ThinHam::Processors::Haml.new
+ end
+
+ it { processor.should be_a_kind_of(ThinHam::Processors::Markup) }
+
+ it "should map 'foo' to file 'foo.haml'" do
+ processor.map_to_file('foo').should == 'foo.haml'
+ end
+
+ describe "#serve" do
+ attr_reader :controller
+ before(:each) do
+ @controller = flexmock(:controller)
+ controller.should_ignore_missing
+ end
+
+ it "should process the haml file" do
+ flexmock(processor).should_receive(:process).once
+
+ processor.serve(fixture('test1.html'))
+ end
+ it "should allow including pieces (other haml files)" do
+ processor.serve(fixture('foo.html')).should match(/bar/)
+ end
+ end
+
+ describe '#process' do
+ it "should turn haml into html" do
+ processor.process(%Q{
+!!!
+!!! XML
+%body
+ Test
+ }, anything).should match(/<body>\s+Test\s+<\/body>/im)
+ end
+ end
+end
View
33 spec/unit/processors/markup_processor_spec.rb
@@ -0,0 +1,33 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+require 'thin_ham/processors/markup'
+
+describe ThinHam::Processors::Markup do
+ attr_reader :processor
+ before(:each) do
+ @processor = ThinHam::Processors::Markup.new
+ end
+
+ describe "#serve" do
+ attr_reader :controller
+ before(:each) do
+ @controller = flexmock(:controller)
+ controller.should_ignore_missing
+ end
+
+ it "should return result from #process" do
+ flexmock(processor).
+ should_receive(:read_markup).
+ should_receive(:process).and_return(:result_from_process)
+
+ processor.serve(:filename).should == :result_from_process
+ end
+ it "should process the markup file" do
+ flexmock(processor).
+ should_receive(:map_to_file).and_return(fixture('test1.html.haml')).
+ should_receive(:process).with(String).once
+
+ processor.serve(:filename)
+ end
+ end
+end
View
25 spec/unit/processors/sass_processor_spec.rb
@@ -0,0 +1,25 @@
+require File.dirname(__FILE__) + '/../../spec_helper'
+
+describe ThinHam::Processors::Sass do
+ attr_reader :processor
+ before(:each) do
+ @processor = ThinHam::Processors::Sass.new
+ end
+
+ it "should inherit functionality from Markup" do
+ processor.should be_a_kind_of(ThinHam::Processors::Markup)
+ end
+
+ describe "#map_to_file" do
+ it { processor.map_to_file('foo').should == 'foo.sass' }
+ end
+
+ describe "#process" do
+ it "should transform Sass into CSS" do
+ processor.process(%Q{
+body
+ :width 100px
+ }).should == "body {\n width: 100px; }\n"
+ end
+ end
+end
View
8 test/test_thin_ham.rb
@@ -1,8 +0,0 @@
-require "test/unit"
-require "thin_ham"
-
-class TestThinHam < Test::Unit::TestCase
- def test_sanity
- flunk "write tests or I will kneecap you"
- end
-end
Please sign in to comment.
Something went wrong with that request. Please try again.