Web から楽にサイトを更新したいというよくあるアレ。
ただの Wiki 形式だと出力できるのが固定に layout された html だけなので、いろいろやっています。
まず何も考えずにページを新規作成すると Markdown として扱われます。
ページの行頭に title ほげふが と入れておくとページタイトルがほげふがになります。
頭に haml *title と入れておくと、 haml としてレンダリングされます。自由なレイアウトのページも Web 上から更新することが出来ます。 haml 内で Ruby のコードを記述することも出来、とても幸せになれます。
頭に builder *title と入れておくと、 builder テンプレートとしてレンダリングされます。 RSS など XML を吐きたい時に便利。 Ruby を書けるので幸せになれます。
頭に raw でいれたものをそのまま吐きます。 js をアップロードしたり貼ったりしたい時に便利。
頭に ruby で ruby のコードとして評価した結果を返す。何かに使えて便利。
以下のソースを読めば分かると思いますが、 /source/app.rb にソースがある場合はそれを使います。ソースすらサイトから更新出来るようにした。
require 'sinatra'
require 'active_record'
require 'haml'
require 'redcarpet'
require 'cgi'
require 'rack/csrf'
require 'logger'
require 'builder'
require 'digest/md5'
BlueCloth = Redcarpet
PASSWORD = "06f5f62397164780d969e64f0bbd057f"
#ActiveRecord::Base.establish_connection(
# :adapter => 'sqlite3',
# :database => 'production.sqlite3'
#)
ActiveRecord::Base.establish_connection({
adapter: "mysql2",
encoding: "utf8",
host: "mysql.ssig33.com",
port: 3306,
database: 'site',
username: 'root',
password: 'kwgw78'
})
ActiveRecord::Base.logger = Logger.new("./database.log")
ActiveRecord::Migrator.migrate("migrate/", nil)
class Page < ActiveRecord::Base
end
begin
code = Page.where(:name => "source/app.rb").order("created_at desc").first
ary = code.body.split("\n")
ary.shift
eval ary.join("\n")
rescue StandardError, LoadError
class Page < ActiveRecord::Base
def html request
if self.body.split("\n").first =~ /^haml/
return self.haml request
elsif self.body.split("\n").first =~ /^ruby/
return "#{self.body}"
elsif self.body.split("\n").first =~ /^builder/ or self.body.split("\n").first =~ /^raw/
return "#{self.body}"
elsif self.body.split("\n").first =~ /^title/
str = ""
ary = self.body.split("\n")
str = ""
ary.shift
str = ary.join("\n")
else
str = self.body
end
html = BlueCloth.new(str, :fenced_code, :gh_blockcode).to_html rescue "#{self.body}"
html
end
def title
if self.body.split("\n").first =~ /title/
str = self.body.split("\n").shift.split("title\ ").last
elsif self.body.split("\n").first =~ /haml/
str = self.body.split("\n").shift.split("haml\ ").last
elsif self.body.split("\n").first =~ /builder/
str = self.body.split("\n").shift.split("builder\ ").last
elsif self.body.split("\n").first =~ /ruby/
str = self.body.split("\n").shift.split("ruby\ ").last
else
str = self.name
end
str.chomp!
str = self.name if str == nil or str == "" or str == "haml"
str.gsub(/=created_at=/, self.created_at.getutc.to_s)
end
def raw
ary = self.body.split("\n")
ary.shift
ary.join("\n")
end
def haml request
ary = self.body.split("\n")
str = ""
ary.shift
str = ary.join("\n")
Haml::Engine.new(str).render
end
def ruby request
ary = self.body.split("\n")
str = ""
ary.shift
str = ary.join("\n")
eval str
end
def builder request
ary = self.body.split("\n")
str = ""
ary.shift
str = ary.join("\n")
xml = ::Builder::XmlMarkup.new(:indent => 2)
eval str
end
def layout
ary = self.body.split("\n")
ary.shift
ary.join("\n")
end
end
configure do
set :logging, false
set :app_file, __FILE__
use Rack::Session::Cookie, :secret => 'fsdjkfhsjkhr23f8fhsdjkvhnsdjhrfuiscflaaadn8or'
use Rack::Csrf, :raise => true
end
get '/' do
@page = Page.where(:name => "index").order("created_at desc").first
redirect '/edit/index' unless @page
if layout = Page.where(:name => "layout").order("created_at desc").first
return haml layout.layout
else
return haml :page
end
end
get %r{/edit/(.*)} do
@id = params[:captures].first.to_s
@page = Page.where(:name => @id).order("created_at desc").first
@page = Page.new if @page == nil
haml :edit
end
get %r{/(.*)} do
id = params[:captures].first.to_s
id.chop! if id.reverse[0] == "/"
file = open("public/#{id}/index.html").read rescue nil
return file if file
@page = Page.where(:name => id).order("created_at desc").first
redirect "/edit/#{id}" unless @page
return @page.haml(request) if @page.body.split("\n").first =~ /^haml/
return @page.raw if @page.body.split("\n").first =~ /^raw/ or @page.body.split("\n").first =~ /^layout/
if @page.body.split("\n").first =~ /^builder/
content_type :xml
return @page.builder(request)
end
return @page.ruby(request) if @page.body.split("\n").first =~ /^ruby/
if layout = Page.where(:name => "layout").order("created_at desc").first
return haml layout.layout
else
return haml :page
end
end
post '/update' do
if Digest::MD5.hexdigest(params[:password]).to_s != PASSWORD
return "ERROR"
else
page = Page.new
page.name = params[:id]
page.body = params[:body]
page.save
redirect "/#{params[:id]}"
end
end
post '/usr2' do
raise if Digest::MD5.hexdigest(params[:password]).to_s != PASSWORD
system "rm site.pid.oldbin"
system "kill -USR2 `cat site.pid`"
system "kill -INT `cat site.pid.oldbin`"
redirect "/"
end
post %r{/(.*)} do
id = params[:captures].first.to_s
id.chop! if id.reverse[0] == "/"
file = open("public/#{id}/index.html").read rescue nil
return file if file
@page = Page.where(:name => id).order("created_at desc").first
redirect "/edit/#{id}" unless @page
return @page.haml(request) if @page.body.split("\n").first =~ /^haml/
return @page.raw if @page.body.split("\n").first =~ /^raw/ or @page.body.split("\n").first =~ /^layout/
if @page.body.split("\n").first =~ /^builder/
content_type :xml
return @page.builder(request)
end
return @page.ruby(request) if @page.body.split("\n").first =~ /^ruby/
if layout = Page.where(:name => "layout").order("created_at desc").first
return haml layout.layout
else
return haml :page
end
end
post '/destroy' do
raise if Digest::MD5.hexdigest(params[:password]).to_s != PASSWORD
Page.where(:name => params[:id]).delete_all
redirect "/"
end
error do
"error... #{env['sinatra.error']}"
end
helpers do
def h str
CGI.escapeHTML str.to_s
end
def title
if request.path_info == "/" or request.path_info == "/index"
return "ssig33.com"
else
return "ssig33.com - #{@page.title}"
end
end
end
end
__END__
@@ page
%meta{:charset => "UTF-8"}
%title=h title
%link{:href => "http://ssig33.com/common.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}
%link{:href => "http://ssig33.com/sh_style.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}
%meta{:name => "viewport", :content => "width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}
%link{:href => '/feed', :rel => 'alternate', :title => 'feed', :type => "application/rss+xml"}
%script{:src => "/sh_main.min.js"}
%script{:src => "/sh_ext.js"}
%script{:src => "/jquery-1.6.1.min.js"}
%div#all~@page.html(request)
:javascript
$.each($("pre code"), function(){
var code = $(this);
var text = code.text();
var pre = $(code.parent());
code.remove();
pre.text(text);
var lang = pre.attr("lang");
if(lang != undefined){
pre.addClass("sh_"+lang);
}
})
sh_highlightDocument();
@@ edit
%meta{:charset => "UTF-8"}
%title=h "Edit - #{params[:captures].first.to_s}"
%link{:href => "http://ssig33.com/common.css", :media => "screen", :rel => "stylesheet", :type => "text/css"}
%meta{:name => "viewport", :content => "width=320, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"}
%div#all
%form{:action => "/update", :method => "post"}
=Rack::Csrf.csrf_tag(env)
%input{:id => "id", :name => "id", :type => "hidden", :value => "#{@id}"}
%p
%textarea{:cols => "80", :id => "bodY", :name => "body", :rows => "30"}=@page.body #rescue ""
%p
%input{:id => "password", :name => "password", :type => "password"}
%p
%input{:name => "commit", :type => "submit", :value => "Save changes"}
%form{:action => "/destroy", :method => "post"}
=Rack::Csrf.csrf_tag(env)
%input{:id => "id", :name => "id", :type => "hidden", :value => "#{@id}"}
%p
%input{:id => "password", :name => "password", :type => "password"}
%p
%input{:name => "commit", :type => "submit", :value => "Destroy"}
%form{:action => "/usr2", :method => "post"}
=Rack::Csrf.csrf_tag(env)
%input{:id => "id", :name => "id", :type => "hidden", :value => "#{@id}"}
%p
%input{:id => "password", :name => "password", :type => "password"}
%p
%input{:name => "commit", :type => "submit", :value => "Destroy"}
%form{:action => "/usr2", :method => "post"}
=Rack::Csrf.csrf_tag(env)
%input{:id => "id", :name => "id", :type => "hidden", :value => "#{@id}"}
%p
%input{:id => "password", :name => "password", :type => "password"}
%p
%input{:name => "commit", :type => "submit", :value => "USR2"}
class CreatePages < ActiveRecord::Migration
def self.up
create_table :pages do |t|
t.string :name
t.text :body
t.timestamps
end
add_index :pages, [:name, :created_at]
add_index :pages, :name
end
def self.down
drop_table :pages
end
end