
Sputnik
Content Management in Lua
Yuri Takhteyev
January 13, 2009




Can use Xavante as the server. Or (fast)cgi.
Both initial installation and for adding plugins later.
LuaFileSystem, MD5, cosmo

LPEG (for cosmo)
lbase64, luasocket, Markdown
LuaSQL, LuaSVN, LuaSQL, lzlib
(All available as Rocks.)


mkdir ~/sputnik
cd ~/sputnik
wget http://luaforge.net/frs/download.php/3468/kepler-install-1.1-1
bash kepler-install-1.1-1 --prefix=${PWD} --without-readline
./bin/luarocks --only-from=http://sputnik.freewisdom.org/rocks/earth install sputnik
./bin/lua -lluarocks.require -e 'require("sputnik").setup()'
mkdir wiki-data && chmod -R a+rw wiki-data

git clone git://gitorious.org/sputnik/mainline.git mainline.git
bash mainline.git/scripts/link_rock.sh -i ${PWD} -g mainline.git

Editable pages, history, diff, RSS, permissions, user accounts.
sputnik/config, sputnik/navigation, etc

(Just plug in a function.)

$do_messages[[<p class="$class">$message</p>]]
<div class='content'>$content</div>
{
do_messages = node.messages,
content = node.inner_html,
}

<p class="notice">Successfully created your new account.\</p>
<div class='content'>\</div>
(See http://cosmo.luaforge.net)

$if_logged_in[[ _(HI_USER) (<a $logout_link>_(LOGOUT)</a>) ]]
HI_USER = {
en_US = "Hi, $user!",
ru = "Превед, $user!",
pt_BR = "Oi, $user!",
}

INTERFACE_LANGUAGE = "ru"
Превед, Медвед!

Configurations, javascript, css, passwords.
See "sputnik" node for a (nearly) complete list.
Yes, everything. Even the icons.
(We'll come back to this.)


~/sputnik
bin/
lua
luarocks, luarocks-admin
xavante-start, wsapi.cgi
rocks/
sputnik, etc
kepler/
htdocs/
sputnik.ws
mainline.git/

~/sputnik/
~/sputnik/bin/lua, luarocks
~/sputnik/rocks/wsapi, lfs, etc.
~/sputnik/bin/xavante-start, wsapi.cgi
~/sputnik/kepler/htdocs/
~/sputnik/rocks/sputnik, etc.
~/sputnik/kepler/htdocs/sputnik.ws
~/sputnik/wiki-data/
~/sputnik/mainline-git/

require('sputnik')
return sputnik.wsapi_app.new{
VERSIUM_PARAMS = { '/home/yuri/sputnik/wiki-data/' },
BASE_URL = '/sputnik.ws',
PASSWORD_SALT = 'ADzkBbB51xfJgsptsDF0Wep1LAJxK0sbuRlWTMRL',
TOKEN_SALT = '9XkgfzAy25oPaEHL4h9E7rFr9ReStVttIEzN4ZbX',
}
(sputnik.cgi looks basically the same.)

wsapi_env -->[wsapi]--> a request
preprocess the request
request.node_id -->[my_sputnik]--> a node
request.command -->[node]--> action_function
request.params -->[action_function]--> content, content_type
(All wrapped in pcalls for error handling.)

A document-oriented hierarchical storage system with history.
(Like ORM but without the R.)

"Versium"
(See http://sputnik.freewisdom.org/en/Versium)


fields = [[
fields = {0.0, proto="concat", activate="lua"}
title = {0.1 }
...
prototype = {0.6 }
...
content = {0.8 }
]]
title = "@Root (Root Prototype)"
content = [[
...
]]
The numbers are for ordering fields. "activate" tells us what to do with the field.

Only use own value
Use own value if set, otherwise of the prototype node
Concatentate own value with that of the prototype node - most useful if content is Lua.

fields = [[
fields = {0.0, proto="concat", activate="lua"} -- Example #2
title = {0.1 }
...
prototype = {0.6 }
permissions = {0.7, proto="concat"} -- Example #1
...
content = {0.8 }
edit_ui = {0.9, proto="concat"} -- Example #3
]]
Note: permissions and edit_ui store Lua code, but are not activated automatically. This is because they require a custom environment.

deny(all_users, all_actions)
allow(all_users, "show")
allow(all_users, "edit")
deny(all_users, all_actions)
allow(Admin, all_actions)
allow(all_users, "login")
allow(all_users, "js")

...
content = {0.8 }
content.activate = "lua"

page_name = {1.1, "readonly_text"}
title = {1.2, "text_field"}
...
content = {3.01, "editor", rows=15, no_label=true}
content = nil
file_upload = {1.30, "file"}
file_description = {1.31, "text_field"}
file_copyright = {1.32, "text_field"}


show = "wiki.show"
show_content = "wiki.show_content"
history = "wiki.history"
edit = "wiki.edit"
show = "binaryfile.show"
save = "binaryfile.save"
download = "binaryfile.download"
png = "binaryfile.mimetype"

(But we are not done yet.)


(A live demo.)


fields= [[
reported_by = {.11}
priority = {.12}
component = {.13}
assigned_to = {.14}
status = {.15}
resolution = {.16}
]]

NODE.edit_ui= [[
reported_by = {1.31, "text_field"}
assigned_to = {1.32, "text_field"}
status = {1.33, "select",
options = {"open", "someday", ... }}
resolution = {1.35, "select",
options = {"n.a.", "fixed", "wontfix"}}
priority = {2.10, "select",
options = {"unassigned", "high", ... }}
page_name = null
]]

actions= [[show = "tickets.show"]]
translations = "tickets/translations"
templates = "tickets/templates"

<table width="100%">
<tr style="background:$status_color">
<td width="15%" style="text-align: right;">
<span style="font-size: 80%">ticket id</span><br/>
<span style="font-size: 200%;">$ticket_id</span>
</td>
<td width="15%" style="text-align: right;">
<span style="font-size: 80%">status</span><br/>
<span style="font-size: 200%">$status</span>
...

$status_color
$ticket_id
$status

actions.show = function(node, request, sputnik)
local parent_id = node.id:match(PARENT_PATTERN)
local index_node = sputnik:get_node(parent_id)
local ticket_info = {
status = node.status
status_color = status_to_color[node.status] or "white",
ticket_id = node.id:gsub(parent_id.."/", ""),
...
}
...
node.inner_html = cosmo.fill(node.templates.SHOW, ticket_info)
return node.wrappers.default(node, request, sputnik)
end


A new prototype node
A custom action to read mbox content
mpop for fetching mail
A Lua script for loading it into Sputnik (66 lines)


http://sputnik.freewisdom.org/en/PUC_Presentation_2009.slides
André Carregal, Bruno Guedes, Dado Sutter, Hisham Muhammad, Jérôme Vuarand, Jim Whitehead, Pierre Pracht, Sérgio Medeiros, Yuri Takhteyev
(see http://sputnik.freewisdom.org/en/Credits/)