• 日常搜索
  • 百度一下
  • Google
  • 在线工具
  • 搜转载

构建CMS:rubyPress

这一次,我使用Ruby语言来创建服务器。我发现通过用多种语言创建相同的程序,您开始对更好地实现程序的方法获得新的见解。您还可以看到更多向程序添加功能的方法。让我们开始吧。

设置和加载库

要使用 Ruby 编程,您需要在系统上安装最新版本。如今,许多操作系统(Linux 和 OS X)都预装了 Ruby,但它们通常具有较旧的版本。本教程假设您拥有 Ruby 2.4 版。

升级到最新版本的 ruby 最简单的方法是使用RVM。要在 Linux 或mac OS X上安装 RVM,请在终端中键入以下内容:

gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -sSL https://get.rvm.io | bash -s stable

这将创建一个安全连接来下载和安装 RVM。这也会安装最新的 Ruby 稳定版本。您必须重新加载 shell 才能完成安装。

对于 Windows,您可以下载Windows Ruby 安装程序。目前,这个包最高到 Ruby 2.2.2,可以很好地运行本教程中的库和脚本

正确安装 Ruby 语言后,您现在可以安装库。Ruby,就像 Go 和node一样,有一个用于安装第三方库的包管理器。在终端中,键入以下内容:

gem install sinatra
gem install ruby-handlebars
gem install kramdown
gem install slim

这将安装Sinatra、Ruby Handlebars、Kramdown和Slim库。Sinatra 是一个 Web 应用程序框架。Ruby Handlebars 在 Ruby 中实现了 Handlebars 模板引擎。Kramdown 是一个 Markdown 到 html 的转换器。Slim 是一个类似玉器的库,但它不包括玉器的宏定义。因此,新闻和博客文章索引中使用的宏现在是正常的 Jade。

创建 rubyPress.rb 文件

在顶层目录中,创建文件rubyPress.rb并添加以下代码。我将评论添加到文件中的每个部分。

#
# Load the Libraries.
#
require 'sinatra'           # http://www.sinatrarb.com/
require 'ruby-handlebars'   # https://github.com/vincent-psarga/ruby-handlebars
require 'kramdown'          # http://kramdown.gettalong.org
require 'slim'              # http://slim-lang.com/
require 'json'
require 'date'

首先要做的是加载库。与 Node.js 不同,这些不会加载到变量中。Ruby 库将它们的功能添加到程序范围内。

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#
# Setup the Handlebars engine.
#
$hbs = Handlebars::Handlebars.new
 
#
# HandleBars Helper:   date
#
# Description:         This helper returns the current date
#                      based on the format given.
#
$hbs.register_helper('date') {|context, format|
    now = Date.today
    now.strftime(format)
}
 
#
# HandleBars Helper:   cdate
#
# Description:         This helper returns the given date
#                      based on the format given.
#
$hbs.register_helper('cdate') {|context, date, format|
    day = Date.parse(date)
    day.strftime(format)
}
 
#
# HandleBars Helper:   save
#
# Description:         This helper expects a
#                      "|" where the name
#                      is saved with the value for future
#                      expansions. It also returns the
#                      value directly.
#
$hbs.register_helper('save') {|context, name, text|
    #
    # If the text parameter isn't there, then it is the
    # goPress format all combined into the name. Split it
    # out. The parameters are not String objects.
    # Therefore, they need converted first.
    #
    name = String.try_convert(name)
    if name.count("|") > 0
        parts = name.split('|')
        name = parts[0]
        text = parts[1]
    end
 
    #
    # Register the new helper.
    #
    $hbs.register_helper(name) {|context, value|
        text
    }
 
    #
    # Return the text.
    #
    text
}

Handlebars 库使用定义的不同辅助函数进行初始化。定义的辅助函数是date、cdate和save。 

助手函数获取当前date日期和时间,并根据传递给助手的格式字符串对其进行格式化。 cdate除了首先通过日期外,其他类似。帮助器save允许您指定 aname和value。它使用名称创建一个新助手name并传回value. 这允许您创建一次指定并影响许多位置的变量。这个函数也接受 Go 版本,它需要一个带有name, '|'的字符串 作为分隔符,并且value.

#
# Load Server data.
#
$parts = {}
$parts = JSON.parse(File.read './server.json')
$styleDir = Dir.getwd + '/themes/styling/' + $parts['CurrentStyling']
$layoutDir = Dir.getwd + '/themes/layouts/' + $parts['CurrentLayout']
 
#
# Load the layouts and styles defaults.
#
$parts["layout"] = File.read $layoutDir + '/template.html'
$parts["404"] = File.read $styleDir + '/404.html'
$parts["footer"] = File.read $styleDir + '/footer.html'
$parts["header"] = File.read $styleDir + '/header.html'
$parts["sidebar"] = File.read $styleDir + '/sidebar.html'
 
#
# Load all the page parts in the parts directory.
#
Dir.entries($parts["Sitebase"] + '/parts/').select {|f|
    if !File.directory? f
        $parts[File.basename(f, ".*")] = File.read $parts["Sitebase"] + '/parts/' + f
    end
}
 
#
# Setup server defaults:
#
port = $parts["ServerAddress"].split(":")[2]
set :port, port

代码的下一部分用于加载网站的可缓存项。这是主题的样式和布局中的所有内容,以及parts子目录中的项目。首先从文件中$parts加载全局变量。server.json然后使用该信息为指定的布局和主题加载适当的项目。Handlebars 模板引擎使用此信息来填写模板。

#
# Define the routes for the CMS.
#
get '/' do
  page "main"
end
 
get '/favicon.ico', :provides => 'ico' do
    File.read "#{$parts['Sitebase']}/images/favicon.ico"
end
 
get '/stylesheets.css', :provides => 'css'  do
    File.read "#{$parts["Sitebase"]}/css/final/final.css"
end
 
get '/scripts.js', :provides => 'js'  do
    File.read "#{$parts["Sitebase"]}/js/final/final.js"
end
 
get '/images/:image', :provides => 'image' do
    File.read "#{$parts['Sitebase']}/images/#{parms['image']}"
end
 
get '/posts/blogs/:blog' do
    post 'blogs', params['blog'], 'index'
end
 
get '/posts/blogs/:blog/:post' do
    post 'blogs', params['blog'], params['post']
end
 
get '/posts/news/:news' do
    post 'news', params['news'], 'index'
end
 
get '/posts/news/:news/:post' do
    post 'news', params['news'], params['post']
end
 
get '/:page' do
    page params['page']
end

下一部分包含所有路由的定义。Sinatra 是一个完整的rest兼容服务器。但是对于这个 CMS,我只会使用get动词。每个路由从路由中获取项目以传递给生成正确页面的函数。在 Sinatra 中,以冒号开头的名称指定要传递给路由处理程序的路由部分。这些项目位于params哈希表中。

#
# Various functions used in the making of the server:
#
 
#
# Function:    page
#
# Description: This function is for processing a page
#              in the CMS.
#
# Inputs:
#               pg  The page name to lookup
#
def page(pg)
    processPage $parts["layout"], "#{$parts["Sitebase"]}/pages/#{pg}"
end

该page函数从路由中获取页面的名称,并将$parts变量中的布局与函数所需的页面文件的完整路径一起传递processPage。该processPage函数获取此信息并创建适当的页面,然后将其返回。在 Ruby 中,最后一个函数的输出是函数的返回值。

#
# Function:    post
#
# Description: This function is for processing a post type
#              page in the CMS. All blog and news pages are
#                   post type pages.
#
# Inputs:
#              type   The type of the post
#              cat    The category of the post (blog, news)
#              post   The actual page of the post
#
def post(type, cat, post)
    processPage $parts["layout"], "#{$parts["Sitebase"]}/posts/#{type}/#{cat}/#{post}"
end

该post功能与该page功能一样,只是它适用于所有帖子类型的页面。这个函数需要 post type、 post category 和它post本身。这些将为要显示的正确页面创建地址。

#
# Function:    figurePage
#
# Description: This function is to figure out the page
#              type (ie: markdown, HTML, jade, etc), read
#              the contents, and translate it to HTML.
#
# Inputs:
#              page      The address of the page 
#                        without its extension.
#
def figurePage(page)
    result = ""
 
    if File.exist? page + ".html"
        #
        # It's an HTML file.
        #
        result = File.read page + ".html"
    elsif File.exist? page + ".md"
        #
        # It's a markdown file.
        #
        result = Kramdown::Document.new(File.read page + ".md").to_html
 
        #
        # Fix the fancy quotes from Kramdown. It kills
        # the Handlebars parser.
        #
        result.gsub!("“","\"")
        result.gsub!("”","\"")
    elsif File.exist? page + ".amber"
        #
        # It's a jade file. Slim doesn't support
        # Macros. Therefore, not as powerful as straight jade.
        # Also, we have to render any Handlebars first
        # since the Slim engine dies on them.
        #
        File.write("./tmp.txt",$hbs.compile(File.read page + ".amber").call($parts))
        result = Slim::Template.new("./tmp.txt").render()
    else
        #
        # Doesn't exist. Give the 404 page.
        #
        result = $parts["404"]
    end
 
    #
    # Return the results.
    #
    return result
end

该figurePage函数使用该processPage函数从文件系统中读取页面内容。此函数接收不带扩展名的文件的完整路径。figurePage然后测试具有给定名称且html扩展名为读取 HTML 文件的文件。第二种选择是 mdMarkdown 文件的扩展名。 

最后,它检查amberJade 文件的扩展名。请记住:Amber 是 Go 中用于处理 Jade 语法文件的库的名称。对于跨功能,我保持不变。一个 HTML 文件被简单地传回,而所有 Markdown 和 Jade 文件在传回之前都被转换为 HTML。

如果未找到文件,用户将收到该404页面。这样,您的“找不到页面”页面看起来就像除了内容之外的任何其他页面。

#
# Function:    processPage
#
# Description: The function processes a page by getting
#              its contents, combining with all the page
#              parts using Handlebars, and processing the
#              shortcodes.
#
# Inputs:
#              layout  The layout structure for the page
#              page    The complete path to the desired
#                      page without its extension.
#
def processPage(layout, page)
    #
    # Get the page contents and name.
    #
    $parts["content"] = figurePage page
    $parts["PageName"] = File.basename page
 
    #
    # Run the page through Handlebars engine.
    #
    begin
        pageHB = $hbs.compile(layout).call($parts)
    rescue
        pageHB = "
Render Error
 
"
    end
 
    #
    # Run the page through the shortcodes processor.
    #
    pageSH = processShortCodes pageHB
 
    #
    # Run the page through the Handlebar engine again.
    #
    begin
        pageFinal = $hbs.compile(pageSH).call($parts)
    rescue
        pageFinal = "
Render Error
 
" + pageSH
    end
 
    #
    # Return the results.
    #
    return pageFinal
end

该processPage函数对页面数据执行所有模板扩展。它首先调用 figurePage函数来获取页面的内容。然后,它使用 Handlebars 处理传递给它的布局以扩展模板。 

然后该processShortCode函数将查找并处理页面中的所有简码。然后将结果第二次传递给 Handlebars 以处理简码留下的任何宏。用户收到最终结果。

#
# Function:    processShortCodes
#
# Description: This function takes the page and processes
#              all of the shortcodes in the page.
#
# Inputs:
#              page     The contents of the page to 
#                       process.
#
def processShortCodes(page)
    #
    # Initialize the result variable for returning.
    #
    result = ""
 
    #
    # Find the first shortcode
    #
    scregFind = /\-\[([^\]]*)\]\-/
    match1 = scregFind.match(page)
    if match1 != nil
        #
        # We found one! get the text before it
        # into the result variable and initialize
        # the name, param, and contents variables.
        #
        name = ""
        param = ""
        contents = ""
        nameLine = match1[1]
        loc1 = scregFind =~ page
        result = page[0, loc1]
 
        #
        # Separate out the nameLine into a shortcode
        # name and parameters.
        #
        match2 = /(\w+)(.*)*/.match(nameLine)
        if match2.length == 2
            #
            # Just a name was found.
            #
            name = match2[1]
        else
            #
            # A name and parameter were found.
            #
            name = match2[1]
            param = match2[2]
        end
 
        #
        # Find the closing shortcode
        #
        rest = page[loc1+match1[0].length, page.length]
        regEnd = Regexp.new("\\-\\[\\/#{name}\\]\\-")
        match3 = regEnd.match(rest)
        if match3 != nil
            #
            # Get the contents the tags enclose.
            #
            loc2 = regEnd =~ rest
            contents = rest[0, loc2]
 
            #
            # Search the contents for shortcodes.
            #
            contents = processShortCodes(contents)
 
            #
            # If the shortcode exists, run it and include
            # the results. Otherwise, add the contents to
            # the result.
            #
            if $shortcodes.include?(name)
                result += $shortcodes[name].call(param, contents)
            else
                result += contents
            end
 
            #
            # process the shortcodes in the rest of the
            # page.
            #
            rest = rest[loc2 + match3[0].length, page.length]
            result += processShortCodes(rest)
        else
            #
            # There wasn't a closure. Therefore, just 
            # send the page back.
            #
            result = page
        end
    else
        #
        # No shortcodes. Just return the page.
        #
        result = page
    end
 
    return result
end

该processShortCodes函数获取给定的文本,查找每个简码,并使用简码的参数和内容运行指定的简码。我也使用简码例程来处理简码的内容。

短代码是一个类似 HTML 的标签,它使用-[和]-来分隔开始标签和-[/结束]-标签。开始标签也包含短代码的参数。因此,一个示例短代码将是:

-[box]-
This is inside a box.
-[/box]-

此简码定义了box不带任何参数的简码,其内容为<p>This is inside a box.</p>. 短代码将box内容包装在适当的 HTML 中,以在文本周围生成一个框,文本位于框的中心。如果您以后想要更改box呈现方式,您只需更改短代码的定义。这样可以节省很多工作。

#
# Data Structure:  $shortcodes
#
# Description:     This data structure contains all
#                  the valid shortcodes names and the
#                  function. All shortcodes should
#                  receive the arguments and the
#                  that the shortcode encompasses.
#
$shortcodes = {
    "box" => lambda { |args, contents|
        return("<div>#{contents}</div>")
    },
   'Column1'=> lambda { |args, contents|
        return("<div>#{contents}</div>")
   },
   'Column2' => lambda { |args, contents|
      return("<div>#{contents}</div>")
   },
   'Column1of3' => lambda { |args, contents|
      return("<div>#{contents}</div>")
   },
   'Column2of3' => lambda { |args, contents|
      return("<div>#{contents}</div>")
   },
   'Column3of3' => lambda { |args, contents|
      return("<div>#{contents}</div>")
   },
   'php' => lambda { |args, contents|
      return("<div><pre class="brush: php noskimlinks noskimwords">#{contents}</pre></div>")
   },
   'js' => lambda { |args, contents|
      return("<div><pre class="brush: javascript noskimlinks noskimwords">#{contents}</pre></div>")
   },
    "html" => lambda { |args, contents|
        return("<div><pre class="brush: html noskimlinks noskimwords">#{contents}</pre></div>")
    },
   'css' => lambda {|args, contents|
      return("<div><pre class="brush: css noskimlinks noskimwords">#{contents}</pre></div>")
   }
}

文件中的最后一件事是$shortcodes包含短代码例程的哈希表。这些是简单的简码,但您可以根据需要创建其他简码。 

所有简码都必须接受两个参数:args和contents. 这些字符串包含简码的参数和简码周围的内容。由于短代码位于哈希表中,因此我使用 lambda 函数来定义它们。lambda 函数是没有名称的函数。运行这些函数的唯一方法是从哈希数组。

运行服务器

rubyPress.rb使用上述内容创建文件后,您可以使用以下命令运行服务器:

ruby rubyPress.rb

由于 Sinatra 框架使用Ruby on Rails Rack 结构,您可以使用Pow来运行服务器。Pow 将设置您系统的主机文件,以便在本地运行您的服务器,就像从托管站点一样。您可以在命令行中使用以下命令安装 Pow with Powder :

gem install powder
powder install

Powder 是一个命令行例程,用于在您的计算机上管理 Pow 站点。要让 Pow 看到您的站点,您必须在目录中创建指向项目目录的软链接~/.pow。如果服务器在/Users/test/Documents/rubyPress目录中,您将执行以下命令:

cd ~/.pow
ln -s /Users/test/Documents/rubyPress rubyPress

ln -s创建指向首先指定的目录的软链接,然后指定名称。然后Pow将在您的系统上使用软链接的名称设置一个域。在上面的示例中,http://rubyPress.dev在浏览器中访问网站将从服务器加载页面。

要启动服务器,请在创建软链接后键入以下内容:

powder start

要在进行一些代码更改后重新加载服务器,请键入以下内容:

powder restart

构建CMS:rubyPress  第1张rubyPress 主页

在浏览器中访问网站会出现上图。Pow 将在http://rubyPress.dev. 无论您使用哪种方法启动站点,您都会看到相同的结果页面。

结论

好吧,你已经做到了。另一个 CMS,但这次是 Ruby。此版本是本系列中创建的所有 CMS 中最短的版本。试验代码,看看如何扩展这个基本框架。


文章目录
  • 设置和加载库
  • 创建 rubyPress.rb 文件
  • 运行服务器
  • 结论