Permalink
Browse files

Initial commit of plain application

  • Loading branch information...
0 parents commit 404d29862d3ae8855cd4e917ed8d91f191a206e4 @karmi committed Oct 26, 2008
@@ -0,0 +1,6 @@
+.DS_Store
+*/.DS_Store
+log/*.log
+tmp/**/*
+config/config.yml
+config/deploy.rb
@@ -0,0 +1,2 @@
+load 'deploy' if respond_to?(:namespace) # cap2 differentiator
+load 'config/deploy'
@@ -0,0 +1,67 @@
+require 'rake'
+
+task :default => 'app:start'
+
+namespace :app do
+
+ desc "Install the fresh application"
+ task :install do
+ # TODO : Copy fixtures into <tt>./data/</tt>, open in Safari on a Mac, etc
+ Rake::Task['app:install:create_db'].invoke
+ end
+ namespace :install do
+ task :create_db do
+ require 'rubygems'
+ require 'activerecord'
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => './data/development.db')
+ load File.join( File.dirname(__FILE__), 'config', 'db_create_comments' )
+ end
+ task :create_sample_article do
+ end
+ task :create_sample_comment do
+ end
+ task :open_in_browser do
+ end
+ end
+
+ desc 'Start application in development'
+ task :start do
+ exec "ruby app/marley.rb"
+ end
+
+ desc "Deploy application on the server [shortcut for Cap]"
+ task :deploy do
+ exec "cap deploy"
+ end
+
+ desc "Run tests for the application"
+ task :test do
+ exec "cd app/test; ruby marley_test.rb"
+ end
+
+end
+
+namespace :blog do
+
+ task :sync do
+ # TODO : use Git
+ exec "cap blog:sync"
+ end
+
+end
+
+namespace :server do
+
+ task :start do
+ exec "thin -R rackup.ru -d -l log/production.log -e production -p 4500 start"
+ end
+
+ task :stop do
+ exec "thin stop"
+ end
+
+ task :restart do
+ exec "thin restart"
+ end
+
+end
@@ -0,0 +1,228 @@
+require 'rubygems'
+require 'ftools' # ... we wanna access the filesystem ...
+require 'yaml' # ... use YAML for configs and stuff ...
+require 'sinatra' # ... Classy web-development dressed in DSL, http://sinatrarb.heroku.com
+require 'activerecord' # ... or Datamapper? What? :)
+require 'rdiscount' # ... convert Markdown into HTML in blazing speed
+require File.join(File.dirname(__FILE__), '..', 'vendor/akismet') # ... filter spam from ham
+
+# ... or alternatively, run Sinatra on edge ...
+# $:.unshift File.dirname(__FILE__) + 'vendor/sinatra/lib'
+# require 'sinatra'
+
+CONFIG = YAML.load_file( File.join(File.dirname(__FILE__), '..', 'config', 'config.yml') ) unless defined? CONFIG
+
+# -----------------------------------------------------------------------------
+
+module Blog
+
+ DATA_DIRECTORY = File.join(File.dirname(__FILE__), '..', 'data') unless defined? DATA_DIRECTORY
+
+ # = Articles
+ # Data source is <tt>../data</tt> directory
+ class Post
+
+ attr_reader :id, :title, :perex, :body, :body_html, :meta, :published_on, :updated_on, :published, :comments
+
+ def initialize(options={})
+ options.each_pair { |key, value| instance_variable_set("@#{key}", value) if self.respond_to? key }
+ end
+
+ def self.all(options={})
+ self.find_all options.merge(:draft => true)
+ end
+
+ def self.published(options={})
+ self.find_all options.merge(:draft => false)
+ end
+
+ def self.[](id, options={})
+ self.find_one(id, options)
+ end
+
+ def categories
+ self.meta['categories'] if self.meta and self.meta['categories']
+ end
+
+ private
+
+ def self.find_all(options={})
+ options[:except] ||= ['body', 'body_html']
+ posts = []
+ self.extract_posts_from_directory(options).each do |file|
+ attributes = self.extract_post_info_from(file, options)
+ attributes.merge!( :comments => Blog::Comment.find_all_by_post_id(attributes[:id], :select => ['id']) )
+ posts << self.new( attributes )
+ end
+ return posts
+ end
+
+ def self.find_one(id, options={})
+ directory = self.load_directories_with_posts(options).select { |dir| dir =~ Regexp.new("#{id}") }
+ options.merge!( {:draft => true} )
+ # FIXME : Refactor this mess!
+ return if directory.empty?
+ directory = directory.first
+ return unless directory or !File.exist?(directory)
+ file = Dir["#{directory}/*.txt"].first
+ self.new( self.extract_post_info_from(file, options).merge( :comments => Blog::Comment.find_all_by_post_id(id) ) )
+ end
+
+ # Returns directories in data directory. Default is published only (no <tt>.draft</tt> in name)
+ def self.load_directories_with_posts(options={})
+ if options[:draft]
+ Dir[File.join(DATA_DIRECTORY, '*')].select { |dir| File.directory?(dir) }
+ else
+ Dir[File.join(DATA_DIRECTORY, '*')].select { |dir| File.directory?(dir) and not dir.include?('.draft') }
+ end
+ end
+
+ # Loads all directories in data directory and returns first <tt>.txt</tt> file in each one
+ def self.extract_posts_from_directory(options={})
+ self.load_directories_with_posts(options).collect { |dir| Dir["#{dir}/*.txt"].first }.compact
+ end
+
+ # Extracts post information from the directory name, file contents, modification time, etc
+ # Returns hash which can be passed to <tt>Blog::Post.new()</tt>
+ # Extracted attributes can be configured with <tt>:except</tt> and <tt>:only</tt> options
+ def self.extract_post_info_from(file, options={})
+ raise ArgumentError, "#{file} is not a readable file" unless File.exist?(file) and File.readable?(file)
+ options[:except] ||= []
+ options[:only] ||= Blog::Post.instance_methods # FIXME: Refaktorovat!!
+ dirname = File.dirname(file).split('/').last
+ file_content = File.read(file)
+ meta_content = file_content.slice!( self.regexp[:meta] )
+ body = file_content.sub( self.regexp[:title], '').sub( self.regexp[:perex], '').strip
+ post = Hash.new
+ # TODO: Cleanup regexp for ID
+ post[:id] = dirname.sub(self.regexp[:id], '\1').sub(/\.draft$/, '')
+ post[:title] = file_content.scan( self.regexp[:title] ).to_s.strip unless options[:except].include? 'title' or
+ not options[:only].include? 'title'
+ post[:perex] = file_content.scan( self.regexp[:perex] ).first.to_s.strip unless options[:except].include? 'perex' or
+ not options[:only].include? 'perex'
+ post[:body] = body unless options[:except].include? 'body' or
+ not options[:only].include? 'body'
+ post[:body_html] = RDiscount::new( body ).to_html unless options[:except].include? 'body_html' or
+ not options[:only].include? 'body_html'
+ post[:meta] = ( meta_content ) ? YAML::load( meta_content.scan( self.regexp[:meta]).to_s ) :
+ nil unless options[:except].include? 'meta' or not options[:only].include? 'meta'
+ post[:published_on] = File.mtime( File.dirname(file) ) unless options[:except].include? 'published_on' or
+ not options[:only].include? 'published_on'
+ post[:updated_on] = File.mtime( file ) unless options[:except].include? 'updated_on' or
+ not options[:only].include? 'updated_on'
+ post[:published] = !dirname.match(/\.draft$/) unless options[:except].include? 'published' or
+ not options[:only].include? 'published'
+ return post
+ end
+
+ def self.regexp
+ { :id => /^\d{0,4}-{0,1}(.*)$/,
+ :title => /^#\s*(.*)$/,
+ :perex => /^([^\#\n]+\n)$/,
+ :meta => /^\{\{\n(.*)\}\}\n$/mi # Multiline Regexp
+ }
+ end
+
+ end
+
+ # = Comments for articles
+ class Comment < ActiveRecord::Base
+
+ ActiveRecord::Base.establish_connection( :adapter => 'sqlite3', :database => File.join(DATA_DIRECTORY, 'development.db') )
+
+ belongs_to :post
+
+ validates_presence_of :author, :email, :body, :post_id
+
+ before_create :check_spam
+
+ private
+
+ def check_spam
+ result = Blog::Akismet.check( self.ip, self.user_agent, self.referrer, nil, 'comment', self.author, self.email, self.url, self.body, nil )
+ puts result.inspect
+ if result
+ self.checked = true
+ self.spam = result
+ end
+ end
+
+ end
+
+ # = Interface to Akismet checking library
+ # TODO : Cleanup
+ class Akismet
+ def self.check( ip, user_agent, referrer, permalink, comment_type, author, email, url, body, other )
+ akismet = ::Akismet.new(::CONFIG['akismet']['key'], ::CONFIG['akismet']['url'])
+ raise ArgumentError, "Invalid Akismet key" unless akismet.verifyAPIKey
+ akismet.commentCheck(ip, user_agent, referrer, permalink, comment_type, author, email, url, body, other)
+ end
+ end
+
+end
+
+# -----------------------------------------------------------------------------
+
+helpers do
+
+ include Rack::Utils
+ alias_method :h, :escape_html
+
+ def human_date(datetime)
+ datetime.strftime('%d. %m. %Y').gsub(/ 0(\d{1})/, ' \1')
+ end
+
+end
+
+configure do
+ set_options :session => true
+end
+
+configure :production do
+ # 404.html
+ not_found do
+ File.read( File.join( File.dirname(__FILE__), 'public', '404.html') )
+ end
+ # 500.html
+ error do
+ File.read( File.join( File.dirname(__FILE__), 'public', '500.html') )
+ end
+end
+
+# -----------------------------------------------------------------------------
+
+get '/' do
+ @posts = Blog::Post.published
+ @page_title = "#{CONFIG['blog']['title']}"
+ erb :index
+end
+
+get '/:post_id.html' do
+ @post = Blog::Post[ params[:post_id] ]
+ throw :halt, [404, 'Post not found' ] unless @post
+ @page_title = "#{@post.title} #{CONFIG['blog']['name']}"
+ erb :post
+end
+
+post '/:post_id/comments' do
+ @post = Blog::Post[ params[:post_id] ]
+ throw :halt, [404, erb(not_found) ] unless @post
+ params.merge!( { :ip => request.env['REMOTE_ADDR'], :user_agent => request.env['HTTP_USER_AGENT'] } )
+ puts params.inspect
+ @comment = Blog::Comment.create( params )
+ if @comment.valid?
+ redirect "/"+params[:post_id].to_s+'.html#comments'
+ else
+ @page_title = "#{@post.title} #{CONFIG['blog']['name']}"
+ erb :post
+ end
+end
+get '/:post_id/comments' do
+ redirect "/"+params[:post_id].to_s+'.html#comments'
+end
+
+get '/about' do
+ "<p style=\"font-family:sans-serif\">I'm running on Sinatra version " + Sinatra::VERSION + '</p>'
+end
+
+# -----------------------------------------------------------------------------
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>404 Not Found</title>
+</head>
+
+<body>
+
+<div class="container">
+ <div>
+ <h1>404!</h1>
+ </div>
+ <h2>This page was not found.
+ Please <a href="/">return to homepage</a> and try to find your way.
+ </h2>
+</div>
+
+</body>
+</html>
@@ -0,0 +1,22 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <title>500 Server Error</title>
+</head>
+
+<body>
+
+<div class="container">
+ <div>
+ <h1>500!</h1>
+ </div>
+ <h2>Something strange happened.
+ Please try to <a href="/">load the homepage</a> and come back later if you see this page again.
+ </h2>
+</div>
+
+</body>
+</html>
No changes.
@@ -0,0 +1,10 @@
+$:.unshift File.dirname(__FILE__) + '/sinatra/lib'
+require 'sinatra'
+
+Sinatra::Application.default_options.merge!(
+ :run => false,
+ :env => :production
+)
+
+require 'application'
+run Sinatra.application
@@ -0,0 +1,5 @@
+# This is the test article
+
+This is the perex of the test article
+
+Lorem ipsum dolor sit amet, **consectetur** adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Oops, something went wrong.

0 comments on commit 404d298

Please sign in to comment.