Advertisement
  1. Code
  2. CMS

Construyendo un CMS: rubyPress

Scroll to top
Read Time: 16 min

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Después de crear una estructura básica del sistema de gestión de contenido (CMS) y el servidor real utilizando Go y Node.js, está listo para probar su mano en otro lenguaje.

Esta vez, estoy usando el lenguaje Ruby para crear el servidor. He descubierto que al crear el mismo programa en varios lenguajes, comienzas a obtener nuevas ideas sobre mejores formas de implementar el programa. También ve más formas de agregar funcionalidad al programa. Empecemos.

Configurar y cargar las bibliotecas

Para programar en Ruby, deberá tener instalada la última versión en su sistema. Muchos sistemas operativos vienen preinstalados con Ruby en la actualidad (Linux y OS X), pero generalmente tienen una versión anterior. Este tutorial asume que tienes Ruby versión 2.4.

La forma más fácil de actualizar a la última versión de ruby es usar RVM. Para instalar RVM en Linux o Mac OS X, escriba lo siguiente en una terminal:

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

Esto creará una conexión segura para descargar e instalar RVM. Esto instala la última versión estable de Ruby también. Deberás volver a cargar tu caparazón para finalizar la instalación.

Para Windows, puede descargar el instalador de Windows Ruby. Actualmente, este paquete corresponde a Ruby 2.2.2, que está bien para ejecutar las bibliotecas y los scripts en este tutorial.

Una vez que Ruby está instalado correctamente, ahora puede instalar las bibliotecas. Ruby, al igual que Go y Node, tiene un administrador de paquetes para instalar bibliotecas de terceros. En la terminal, escriba lo siguiente:

1
gem install sinatra
2
gem install ruby-handlebars
3
gem install kramdown
4
gem install slim

Esto instala las bibliotecas Sinatra, Ruby Handlebars, Kramdown y Slim. Sinatra es un marco de aplicacion. Ruby Handlebars implementa el motor de plantillas Handlebars en Ruby. Kramdown es un convertidor Markdown to HTML. Slim es una biblioteca similar a Jade, pero no incluye las definiciones macro de Jade. Por lo tanto, las macros utilizadas en los índices de publicaciones de Noticias y Blog ahora son Jade normales.

Creando el archivo rubyPress.rb

En el directorio superior, crea el archivo rubyPress.rb y agrega el siguiente código. Voy a comentar acerca de cada sección a medida que se agrega al archivo.

1
#

2
# Load the Libraries.

3
#

4
require 'sinatra'  		# http://www.sinatrarb.com/

5
require 'ruby-handlebars' 	# https://github.com/vincent-psarga/ruby-handlebars

6
require 'kramdown' 			# http://kramdown.gettalong.org

7
require 'slim' 				# http://slim-lang.com/

8
require 'json'
9
require 'date'

Lo primero que debe hacer es cargar las bibliotecas. A diferencia de Node.js, estos no se cargan en una variable. Las bibliotecas de Ruby agregan sus funciones al alcance del programa.

1
#

2
# Setup the Handlebars engine.

3
#

4
$hbs = Handlebars::Handlebars.new
5
6
#

7
# HandleBars Helper:   date

8
#

9
# Description:         This helper returns the current date

10
#                      based on the format given.

11
#

12
$hbs.register_helper('date') {|context, format|
13
	now = Date.today
14
	now.strftime(format)
15
}
16
17
#

18
# HandleBars Helper:   cdate

19
#

20
# Description:         This helper returns the given date

21
#                      based on the format given.

22
#

23
$hbs.register_helper('cdate') {|context, date, format|
24
	day = Date.parse(date)
25
	day.strftime(format)
26
}
27
28
#

29
# HandleBars Helper:   save

30
#

31
# Description:         This helper expects a

32
#                      "|" where the name

33
#                      is saved with the value for future

34
#                      expansions. It also returns the

35
#                      value directly.

36
#

37
$hbs.register_helper('save') {|context, name, text|
38
	#

39
	# If the text parameter isn't there, then it is the 

40
	# goPress format all combined into the name. Split it 

41
	# out. The parameters are not String objects. 

42
	# Therefore, they need converted first.

43
	#

44
	name = String.try_convert(name)
45
	if name.count("|") > 0
46
		parts = name.split('|')
47
		name = parts[0]
48
		text = parts[1]
49
	end
50
51
	#

52
	# Register the new helper.

53
	#

54
	$hbs.register_helper(name) {|context, value|
55
		text
56
	}
57
58
	#

59
	# Return the text.

60
	#

61
	text
62
}

La biblioteca Handlebars se inicializa con las diferentes funciones auxiliares definidas. Las funciones auxiliares definidas son date, cdate, y save.

La función helper date toma la fecha y hora actual, y la formatea de acuerdo con la cadena de formato que se pasa al helper. cdate es similar excepto para pasar la fecha primero. save Helper le permite especificar un name y un valu. Crea un nuevo ayudante con el name y devuelve el value. Esto le permite crear variables que se especifican una vez y afectan a muchas ubicaciones. Esta función también toma la versión Go, que espera una cadena con el name, '|' como separador y value.

1
#

2
# Load Server Data.

3
#

4
$parts = {}
5
$parts = JSON.parse(File.read './server.json')
6
$styleDir = Dir.getwd + '/themes/styling/' + $parts['CurrentStyling']
7
$layoutDir = Dir.getwd + '/themes/layouts/' + $parts['CurrentLayout']
8
9
#

10
# Load the layouts and styles defaults.

11
#

12
$parts["layout"] = File.read $layoutDir + '/template.html'
13
$parts["404"] = File.read $styleDir + '/404.html'
14
$parts["footer"] = File.read $styleDir + '/footer.html'
15
$parts["header"] = File.read $styleDir + '/header.html'
16
$parts["sidebar"] = File.read $styleDir + '/sidebar.html'
17
18
#

19
# Load all the page parts in the parts directory.

20
#

21
Dir.entries($parts["Sitebase"] + '/parts/').select {|f|
22
	if !File.directory? f
23
		$parts[File.basename(f, ".*")] = File.read $parts["Sitebase"] + '/parts/' + f
24
	end
25
}
26
27
#

28
# Setup server defaults:

29
#

30
port = $parts["ServerAddress"].split(":")[2]
31
set :port, port

La siguiente parte del código es para cargar los elementos almacenables en caché del sitio web. Esto es todo en los estilos y el diseño de su tema, y los elementos en el subdirectorio parts. Una variable global, $parts, se carga primero desde el archivo server.json. Esa información se utiliza para cargar los elementos adecuados para el diseño y el tema especificado. El motor de plantillas de manillar utiliza esta información para completar las plantillas.

1
#

2
# Define the routes for the CMS.

3
#

4
get '/' do
5
  page "main"
6
end
7
8
get '/favicon.ico', :provides => 'ico' do
9
	File.read "#{$parts['Sitebase']}/images/favicon.ico"
10
end
11
12
get '/stylesheets.css', :provides => 'css'  do
13
	File.read "#{$parts["Sitebase"]}/css/final/final.css"
14
end
15
16
get '/scripts.js', :provides => 'js'  do
17
	File.read "#{$parts["Sitebase"]}/js/final/final.js"
18
end
19
20
get '/images/:image', :provides => 'image' do
21
	File.read "#{$parts['Sitebase']}/images/#{parms['image']}"
22
end
23
24
get '/posts/blogs/:blog' do
25
	post 'blogs', params['blog'], 'index'
26
end
27
28
get '/posts/blogs/:blog/:post' do
29
	post 'blogs', params['blog'], params['post']
30
end
31
32
get '/posts/news/:news' do
33
	post 'news', params['news'], 'index'
34
end
35
36
get '/posts/news/:news/:post' do
37
	post 'news', params['news'], params['post']
38
end
39
40
get '/:page' do
41
	page params['page']
42
end

La siguiente sección contiene las definiciones para todas las rutas. Sinatra es un servidor completo que cumple con REST. Pero para este CMS, solo usaré el verbo get. Cada ruta toma los elementos de la ruta para pasar a las funciones para producir la página correcta. En Sinatra, un nombre precedido por dos puntos especifica una sección de la ruta para pasar al controlador de ruta. Estos artículos están en una tabla hash params.

1
#

2
# Various functions used in the making of the server:

3
#

4
5
#

6
# Function:    page

7
#

8
# Description: This function is for processing a page

9
#              in the CMS.

10
#

11
# Inputs:

12
# 				pg 	The page name to lookup

13
#

14
def page(pg)
15
	processPage $parts["layout"], "#{$parts["Sitebase"]}/pages/#{pg}"
16
end

La función page obtiene el nombre de una página de la ruta y pasa el diseño en la variable $parts junto con la ruta completa al archivo de página necesario para la función processPage. La función processPage toma esta información y crea la página adecuada, que luego devuelve. En Ruby, la salida de la última función es el valor de retorno para la función.

1
#

2
# Function:    post

3
#

4
# Description: This function is for processing a post type

5
#              page in the CMS. All blog and news pages are

6
#    				post type pages.

7
#

8
# Inputs:

9
#              type   The type of the post

10
#              cat    The category of the post (blog, news)

11
#              post   The actual page of the post

12
#

13
def post(type, cat, post)
14
	processPage $parts["layout"], "#{$parts["Sitebase"]}/posts/#{type}/#{cat}/#{post}"
15
end

La función post es como la función page, excepto que funciona para todas las páginas de tipo de publicación. Esta función espera el type de publicación, la categoría de publicación y la post misma. Estos crearán la dirección para que se muestre la página correcta.

1
#

2
# Function:    figurePage

3
#

4
# Description: This function is to figure out the page

5
#              type (ie: markdown, HTML, jade, etc), read

6
#              the contents, and translate it to HTML.

7
#

8
# Inputs:

9
#              page      The address of the page 

10
#                        without its extension.

11
#

12
def figurePage(page)
13
	result = ""
14
15
	if File.exist? page + ".html"
16
		#

17
		# It's an HTML file.

18
		#

19
		result = File.read page + ".html"
20
	elsif File.exist? page + ".md"
21
		#

22
		# It's a markdown file.

23
		#

24
		result = Kramdown::Document.new(File.read page + ".md").to_html
25
26
		#

27
		# Fix the fancy quotes from Kramdown. It kills

28
		# the Handlebars parser.

29
		#

30
		result.gsub!("“","\"")
31
		result.gsub!("”","\"")
32
	elsif File.exist? page + ".amber"
33
		#

34
		# It's a jade file. Slim doesn't support

35
		# macros. Therefore, not as powerful as straight jade.

36
		# Also, we have to render any Handlebars first

37
		# since the Slim engine dies on them.

38
		#

39
		File.write("./tmp.txt",$hbs.compile(File.read page + ".amber").call($parts))
40
		result = Slim::Template.new("./tmp.txt").render()
41
	else
42
		#

43
		# Doesn't exist. Give the 404 page.

44
		#

45
		result = $parts["404"]
46
	end
47
48
	#

49
	# Return the results.

50
	#

51
	return result
52
end

La función figurePage usa la función processPage para leer el contenido de la página del sistema de archivos. Esta función recibe la ruta completa al archivo sin la extensión. FigurePage luego prueba un archivo con el nombre dado con la extensión html para leer un archivo HTML. La segunda opción es para una extensión md para un archivo Markdown.

Por último, comprueba si hay una extensión amber para un archivo de Jade. Recuerde: Amber es el nombre de la biblioteca para procesar los archivos de sintaxis de Jade en Go. Lo mantuve igual para inter-funcionalidad. Un archivo HTML simplemente se devuelve, mientras que todos los archivos Markdown y Jade se convierten a HTML antes de volver.

Si no se encuentra un archivo, el usuario recibirá la página 404. De esta manera, su página "página no encontrada" se parece a cualquier otra página excepto por los contenidos.

1
#

2
# Function:    processPage

3
#

4
# Description: The function processes a page by getting

5
#              its contents, combining with all the page

6
#              parts using Handlebars, and processing the

7
#              shortcodes.

8
#

9
# Inputs:

10
#              layout  The layout structure for the page

11
#              page    The complete path to the desired

12
#                      page without its extension.

13
#

14
def processPage(layout, page)
15
	#

16
	# Get the page contents and name.

17
	#

18
	$parts["content"] = figurePage page
19
	$parts["PageName"] = File.basename page
20
21
	#

22
	# Run the page through Handlebars engine.

23
	#

24
	begin
25
		pageHB = $hbs.compile(layout).call($parts)
26
	rescue
27
		pageHB = "

28
Render Error

29


30
"
31
	end
32
33
	#

34
	# Run the page through the shortcodes processor.

35
	#

36
	pageSH = processShortCodes pageHB
37
38
	#

39
	# Run the page through the Handlebar engine again.

40
	#

41
	begin
42
		pageFinal = $hbs.compile(pageSH).call($parts)
43
	rescue
44
		pageFinal = "

45
Render Error

46


47
" + pageSH
48
	end
49
50
	#

51
	# Return the results.

52
	#

53
	return pageFinal
54
end

La función processPage realiza todas las expansiones de plantilla en los datos de página. Comienza llamando a la función figurePage para obtener los contenidos de la página. A continuación, procesa el diseño que se le pasó con Handlebars para expandir la plantilla.

Luego la función processShortCode encontrará y procesará todos los códigos cortos en la página. Los resultados se pasan a Handlebars por segunda vez para procesar las macros dejadas por los shortcodes. El usuario recibe los resultados finales.

1
#

2
# Function:    processShortCodes

3
#

4
# Description: This function takes the page and processes

5
#              all of the shortcodes in the page.

6
#

7
# Inputs:

8
#              page     The contents of the page to 

9
#                       process.

10
#

11
def processShortCodes(page)
12
	#

13
	# Initialize the result variable for returning.

14
	#

15
	result = ""
16
17
	#

18
	# Find the first shortcode

19
	#

20
	scregFind = /\-\[([^\]]*)\]\-/
21
	match1 = scregFind.match(page)
22
	if match1 != nil
23
		#

24
		# We found one! get the text before it

25
		# into the result variable and initialize

26
		# the name, param, and contents variables.

27
		#

28
		name = ""
29
		param = ""
30
		contents = ""
31
		nameLine = match1[1]
32
		loc1 = scregFind =~ page
33
		result = page[0, loc1]
34
35
		#

36
		# Separate out the nameLine into a shortcode

37
		# name and parameters.

38
		#

39
		match2 = /(\w+)(.*)*/.match(nameLine)
40
		if match2.length == 2
41
			#

42
			# Just a name was found.

43
			#

44
			name = match2[1]
45
		else
46
			#

47
			# A name and parameter were found.

48
			#

49
			name = match2[1]
50
			param = match2[2]
51
		end
52
53
		#

54
		# Find the closing shortcode

55
		#

56
		rest = page[loc1+match1[0].length, page.length]
57
		regEnd = Regexp.new("\\-\\[\\/#{name}\\]\\-")
58
		match3 = regEnd.match(rest)
59
		if match3 != nil
60
			#

61
			# Get the contents the tags enclose.

62
			#

63
			loc2 = regEnd =~ rest
64
			contents = rest[0, loc2]
65
66
			#

67
			# Search the contents for shortcodes.

68
			#

69
			contents = processShortCodes(contents)
70
71
			#

72
			# If the shortcode exists, run it and include

73
			# the results. Otherwise, add the contents to

74
			# the result.

75
			#

76
			if $shortcodes.include?(name)
77
				result += $shortcodes[name].call(param, contents)
78
			else
79
				result += contents
80
			end
81
82
			#

83
			# process the shortcodes in the rest of the

84
			# page.

85
			#

86
			rest = rest[loc2 + match3[0].length, page.length]
87
			result += processShortCodes(rest)
88
		else
89
			#

90
			# There wasn't a closure. Therefore, just 

91
			# send the page back.

92
			#

93
			result = page
94
		end
95
	else
96
		#

97
		# No shortcodes. Just return the page.

98
		#

99
		result = page
100
	end
101
102
	return result
103
end

La función processShortCodes toma el texto dado, encuentra cada shortcode y ejecuta el shortcode especificado con los argumentos y el contenido del shortcode. Utilizo la rutina shortcode para procesar los contenidos de códigos cortos también.

Un shortcode es una etiqueta similar a HTML que usa -[ y ]- para delimitar la etiqueta de apertura y -[/ y ]- la etiqueta de cierre. La etiqueta de apertura también contiene los parámetros para el código corto. Por lo tanto, un shortcode de ejemplo sería:

1
-[box]-
2
This is inside a box.
3
-[/box]-

Este shortcode define el shortcode de box sin ningún parámetro con los contenidos de <p>This is inside a box.</p>. El código abreviado box ajusta los contenidos en el HTML apropiado para producir un cuadro alrededor del texto con el texto centrado en el box. Si luego desea cambiar la forma en que se representa el box, solo debe cambiar la definición del código abreviado. Esto ahorra mucho trabajo.

1
#

2
# Data Structure:  $shortcodes

3
#

4
# Description:     This data structure contains all

5
#                  the valid shortcodes names and the

6
#                  function. All shortcodes should

7
#                  receive the arguments and the

8
#                  that the shortcode encompasses.

9
#

10
$shortcodes = {
11
	"box" => lambda { |args, contents|
12
		return("#{contents}")
13
	},
14
   'Column1'=> lambda { |args, contents|
15
		return("#{contents}")
16
   },
17
   'Column2' => lambda { |args, contents|
18
      return("#{contents}")
19
   },
20
   'Column1of3' => lambda { |args, contents|
21
      return("#{contents}")
22
   },
23
   'Column2of3' => lambda { |args, contents|
24
      return("#{contents}")
25
   },
26
   'Column3of3' => lambda { |args, contents|
27
      return("#{contents}")
28
   },
29
   'php' => lambda { |args, contents|
30
      return("#{contents}") }, 'js' => lambda { |args, contents| return("#{contents}") }, "html" => lambda { |args, contents| return("#{contents}") }, 'css' => lambda {|args, contents| return("#{contents}") } }

Lo último en el archivo es la tabla de hash $shortcodes que contiene las rutinas shortcode. Estos son códigos cortos simples, pero puede crear otros códigos cortos para que sean lo más complejos que desee.

Todos los shortcodes tienen que aceptar dos parámetros: args y contents. Estas cadenas contienen los parámetros del shortcode y los contenidos que rodean a los códigos cortos. Como los códigos cortos están dentro de una tabla hash, utilicé una función lambda para definirlos. Una función lambda es una función sin nombre. La única forma de ejecutar estas funciones es desde la matriz hash.

Ejecutando el Servidor

Una vez que haya creado el archivo rubyPress.rb con los contenidos anteriores, puede ejecutar el servidor con:

1
ruby rubyPress.rb

Como el marco de Sinatra funciona con la estructura Ruby on Rails Rack, puede usar Pow para ejecutar el servidor. Pow configurará los archivos host de su sistema para ejecutar su servidor localmente de la misma manera que lo haría desde un sitio alojado. Puede instalar Pow with Powder usando los siguientes comandos en la línea de comando:

1
gem install powder
2
powder install

Powder es una rutina de línea de comandos para administrar sitios Pow en tu computadora. Para que Pow vea su sitio, debe crear un enlace suave al directorio de su proyecto en el directorio ~/.pow Si el servidor está en el directorio /Users/test/Documents/rubyPress, ejecutará los siguientes comandos:

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

El ln -s crea un soft link (enlace suave) al directorio especificado primero, con el nombre especificado en segundo lugar. Pow luego configurará un dominio en su sistema con el nombre del enlace suave. En el ejemplo anterior, ir al sitio web http://rubyPress.dev en el navegador cargará la página desde el servidor.

Para iniciar el servidor, escriba lo siguiente después de crear el enlace suave:

1
powder start

1inicio en polvoPara volver a cargar el servidor después de hacer algunos cambios de código, escriba lo siguiente:

1
powder restart
rubyPress Main PagerubyPress Main PagerubyPress Main Page
RubyPress Página principal

Ir al sitio web en el navegador dará como resultado la imagen de arriba. Pow establecerá el sitio en http://rubyPress.dev. Independientemente del método que utilice para iniciar el sitio, verá la misma página resultante.

Conclusión

Bueno, lo has hecho. Otro CMS, pero esta vez en Ruby. Esta versión es la versión más corta de todos los CMS creados en esta serie. Experimente con el código y vea cómo puede ampliar este marco básico.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.