このサイトのソース

ちょっとした解説

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 にソースがある場合はそれを使います。ソースすらサイトから更新出来るようにした。

app.rb

require 'bundler'
Bundler.require

REDIS = Redis::Namespace.new :ssig33com, url: (ENV['REDIS_URL'] || 'redis://localhost:6379/')

PASSWORD = open('password').read.chomp

ActiveRecord::Base.establish_connection JSON.parse(open('dbconfig.json').read)
ActiveRecord::Base.logger = Logger.new(STDOUT)

AWS_ID=ENV['AWS_ID']
AWS_KEY=ENV['AWS_KEY']

class Page < ActiveRecord::Base
end

configure do
  set :allow_origin, :any
  set :allow_methods, [:get, :post]
  set :allow_credentials, true
  set :server, :puma
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,SyntaxError => e
  p e
  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) xml.to_s 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 %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"}

migrate/0_create_pages.rb

class CreatePages < ActiveRecord::Migration
  def self.up
    create_table :pages do |t|
      t.string :name
      t.text :body, limit: 16777216
      t.timestamps
    end
    add_index :pages, [:name, :created_at]
    add_index :pages, :name
  end

  def self.down
    drop_table :pages
  end
end