Major refactoring (big thanks to OldCoder) enabling CLI and local storage and cleaner modpol/MT split

This commit is contained in:
Nathan Schneider 2021-01-28 23:22:06 -07:00
parent c8ca1d3d51
commit c927b4d9fc
16 changed files with 1833 additions and 260 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/data

View File

@ -1,23 +1,57 @@
# modpol: Modular Politics for Minetest
# modpol: Modular Politics Prototype for Minetest
This is a mod for [Minetest](https://minetest.net) that provides an API for diverse governance mechanisms. It seeks to implement the [Modular Politics](https://metagov.org/modpol) proposal.
This is a mod for [Minetest](https://minetest.net) that enables diverse governance mechanisms. It seeks to implement the [Modular Politics](https://metagov.org/modpol) proposal. Ideally, in the future, it
will be possible to use this framework to simulate governance in a
number of platform contexts.
This mod produces an API that can serve as a dependency for other mods that add specific governance functionalities.
This mod produces an API that can serve as a dependency for other mods that add specific governance functionalities.
Currently modpol requires a Unix-style system. But it is intended to become more fully platform independent. Here, only the init.lua (and the optional storage-mod_storage.lua) files are Minetest-specific.
For background information and project roadmap, see [the wiki](https://gitlab.com/medlabboulder/modpol/-/wikis/home).
## Functioning commands
## Command line
Most of these commands will later be buried under other commands that do more privilege checking. These are mainly for testing purposes.
To interact with the interpreter on Unix systems in CLI mode, install
lua or luajit and execute the following command in this directory:
* `/neworg [orgname]` - Create a new org
```
$ lua
> dofile("modpol.lua")
```
## Minetest
To use this in Minetest, simply install it as a Minetest mod. Minetest
will load init.lua. See the source code for information about chat
commands which can then be used.
Most of these commands will later be buried under other commands that
do more privilege checking. These are mainly for testing purposes.
* `/addorg [orgname]` - Create a new org
* `/listorgs` - Lists the orgs (and their members) currently in existence
* `/rmorgs` - Reset orgs to just "instance" with all online players
* `/listplayers` - Lists all the players currently in the game
* `/joinorg [orgname]` - Adds the user to the specified org
* `/listmembers [orgname]` - Lists the players currently in the specified org
* `/pollself [question]` - Asks the player a yes/no/abstain question
---
Initiated by [Nathan Schneider](https://nathanschneider.info) of the [Media Enterprise Design Lab](https://colorado.edu/lab/medlab) at the University of Colorado Boulder, as part of the [Metagovernance Project](https://metagov.org), in collaboration with the Minetest community—see the initial [forum post](https://forum.minetest.net/viewtopic.php?f=47&t=26037). Contributions welcome.
## Storage
By default, a data directory named "data" will be created in this directory. "/data" will contain a log file and serialized program data files.
Another storage method may be chosen in modpol.lua. A StorageRef-based method for Minetest 5.* is included: storage-mod_storage.lua.
## Authorship
Initiated by [Nathan Schneider](https://nathanschneider.info) of the [Media Enterprise Design Lab](https://colorado.edu/lab/medlab) at the University of Colorado Boulder, as part of the [Metagovernance Project](https://metagov.org). Based on the paper "[Modular Politics: Toward a Governance Layer for Online Communities](https://metagov.org/modpol)."
We'd love to have more contributors, particularly from the Minetest community! Please join the conversation in the [Issues](https://gitlab.com/medlabboulder/modpol/-/issues) or the [Minetest.net forum](https://forum.minetest.net/viewtopic.php?f=47&t=26037).
Thanks to contributors: Robert Kiraly [[OldCoder](https://github.com/oldcoder/)] (ocutils.lua, storage-local.lua, project refactoring)
## Licenses
* [Project](LICENSE.mt): MIT-based Hippocratic License
* [Lua Serpent Serializer](serpent/LICENSE.txt): MIT

1
depends.txt Normal file
View File

@ -0,0 +1 @@
default

363
init.lua
View File

@ -1,227 +1,45 @@
--[[ INITIALIZING: basics ]]--
-- ===================================================================
-- /init.lua
-- Modular Politics (modpol) for Minetest
-- TKTK Maybe make this just a quick ref file and locate MT files elsewhere?
-- TKTK need to add player to orgs.instance with on_joinplayer
-- global API table
modpol = {
}
-- ===================================================================
-- Load modpol system
-- table for all active governance data
modpol.orgs = {
}
dofile(minetest.get_modpath("modpol") .. "/modpol.lua")
-- record of governance interactions
-- every state change should appear here
modpol.ledger = {
}
-- ===================================================================
-- Modular Politics functions
-- Overwriting default API functions with platform-specific ones
-- ===================================================================
-- update from mod_storage
-- https://dev.minetest.net/StorageRef
local mod_storage = minetest.get_mod_storage()
-- load orgs
local stored_orgs = minetest.deserialize(mod_storage:get_string("orgs"))
if (stored_orgs ~= nil) then
modpol.orgs = stored_orgs
end
-- load orgs
local stored_ledger = minetest.deserialize(mod_storage:get_string("ledger"))
if (stored_ledger ~= nil) then
modpol.ledger = stored_ledger
end
--[[ FUNCTIONS:basics ]]--
-- record(message, org)
-- writes all governance events to storage and ledger
function modpol.record(message, org)
-- record to ledger
table.insert(modpol.ledger, message)
-- record to org_ledger
if (modpol.orgs[org] ~= nil) then
local org_ledg = modpol.orgs[org]["ledger"]
if (org_ledg == nil) then
modpol.orgs[org]["ledger"] = {message}
else
modpol.orgs[org]["ledger"] = table.insert(org_ledg,message)
end
end
-- record to storage
mod_storage:set_string("orgs", minetest.serialize(modpol.orgs))
mod_storage:set_string("ledger", minetest.serialize(modpol.ledger))
end
--[[ FUNCTIONS:orgs ]]--
-- new_org()
-- create an org and add it to the list
function modpol.new_org(orgName, orgMembers)
if (orgName == "") then -- blank orgName input
return "-!- Org needs a name"
end
if (modpol.orgs[orgName] == nil) then -- copy check
modpol.orgs[orgName] = {members = orgMembers}
else return "-!- Org already exists"
end
local message = "New org: " .. orgName ..
" (" .. table.concat(orgMembers, ", ") .. ")"
modpol.record(message, orgName)
return message
end
-- /neworg [name]
-- creates a new org with the current user as sole member
minetest.register_chatcommand(
"neworg", {
privs = {},
func = function(user, param)
local result =
modpol.new_org(param,{user})
return true, result
end,
})
-- rename_org()
function modpol.rename_org(oldName, newName)
-- TKTK
local message = "Org renamed: " .. oldName .. " > " .. newName
modpol.record(message, newName)
end
-- /listorgs
-- lists the orgs currently in the game
minetest.register_chatcommand(
"listorgs", {
privs = {},
func = function(user, param)
local orglist = ""
for key, value in pairs(modpol.orgs) do
-- first, set up member list
local membs = modpol.orgs[key]["members"]
if (membs == nil) then membs = ""
else membs = " (" .. table.concat(membs, ", ") .. ")"
end
-- now, assemble the list
if (orglist == "") -- first element only
then orglist = key .. membs
else orglist = orglist .. ", " .. key .. membs
end
end
return true, "Orgs: " .. orglist
end,
})
-- rm_orgs()
-- removes all orgs
function modpol.rm_orgs()
modpol.orgs["instance"] = {members = modpol.list_members()}
local message = "Orgs purged"
modpol.record(message, nil)
return message
end
-- /rmorgs
minetest.register_chatcommand(
"rmorgs", {
privs = {},
func = function(user, param)
modpol.orgs = {}
return true, modpol.rm_orgs()
end,
})
--[[ FUNCTIONS:users ]]--
-- list_members(domain)
-- produces a table with names of players currently in the game
-- if empty, lists all players; if an org name, lists its members
function modpol.list_members(domain)
local members = {}
if (domain == nil) then -- no specified domain; all players
-- ===================================================================
-- Function: modpol.list_users(org)
-- Overwrites function at /users.lua
-- Params:
-- if nil, lists instance members; if an org name, lists its members
-- Output: a table with names of players currently in the game
modpol.list_users = function(org)
local users = {}
if (org == nil) then -- no specified org; all players
for _,player in ipairs(minetest.get_connected_players()) do
local name = player:get_player_name()
table.insert(members,name)
table.insert(users,name)
end
else -- if an org is specified
if (modpol.orgs[domain] ~= nil) then -- org exists
members = modpol.orgs[domain]["members"]
if (modpol.orgs[org] ~= nil) then -- org exists
users = modpol.orgs[org]["members"]
end
end
return members
end
-- /listplayers
minetest.register_chatcommand(
"listplayers", {
privs = {},
func = function(user)
local result = table.concat(modpol.list_members(),", ")
return true, "All players: " .. result
end,
})
function modpol.add_member(org, member)
if (modpol.orgs.org == nil) then
return "-!- No such org"
else
table.insert(modpol.orgs.org["members"], member)
local message = member .. " added to org " .. org
modpol.record(message, org)
return message
end
end
-- /joinorg
minetest.register_chatcommand(
"joinorg", {
privs = {},
func = function(user, param)
local result = modpol.add_member(param, user)
return true, result
end,
})
function modpol.remove_member(org, member)
-- remove from all child orgs also
local message = member .. " removed from org " .. org
modpol.record(message, org)
return message
end
-- /listmembers [org]
-- lists the members of an org
minetest.register_chatcommand(
"listmembers", {
privs = {},
func = function(user, param)
local orglist = modpol.list_members(param)
return true, param .. ": " .. table.concat(orglist,", ")
end,
})
-- PERMISSIONS FUNCTIONS
-- PRIVILEGE FUNCTIONS
-- Minetest-specific
-- manages user privileges according to org membership
function modpol.assign_privilege(org, privilege)
-- add privilege to all members of an org
end
function modpol.remove_privilege(org, privilege)
-- remove privilege from all members of an org, unless they have it from other orgs
end
--[[ USER INTERACTIONS ]]--
-- modpol.binary_poll_user(user, question)
-- ===================================================================
-- Function: modpol.binary_poll_user(user, question)
-- Overwrites function at /interactions.lua
-- presents a yes/no/abstain poll to a user, returns answer
function modpol.binary_poll_user(user, question)
modpol.binary_poll_user = function(user, question)
-- set up formspec
local text = "Poll: " .. question
local formspec = {
@ -238,6 +56,90 @@ function modpol.binary_poll_user(user, question)
minetest.show_formspec(user, "modpol:binary_poll", formspec_string)
end
-- ===================================================================
-- Minetest commands
-- ===================================================================
local chat_table -- MT chat command definitions table
local regchat -- Chat-command registration function
regchat = minetest.register_chatcommand
-- ===================================================================
-- /addorg /add_org
-- This code defines a chat command which creates a new
-- "org". Presently, the command makes the user the sole member of the
-- "org".
chat_table = {
privs = {} ,
func = function (user, param)
local result = modpol.add_org (param, { user })
return true, result
end
}
regchat ("addorg" , chat_table)
regchat ("add_org" , chat_table)
-- ===================================================================
-- /listorg /listorgs /list_org /list_orgs
-- In Minetest mode, this code defines a chat command which lists the
-- existing "orgs".
-- The list shows one "org" per line in the following format:
-- org_name (member, member, ...)
chat_table = {
privs = {} ,
func = function (user, param)
return true, "Orgs:\n" .. modpol.list_orgs()
end
}
regchat ("listorg" , chat_table)
regchat ("listorgs" , chat_table)
regchat ("list_org" , chat_table)
regchat ("list_orgs" , chat_table)
-- ===================================================================
-- /listplayers
minetest.register_chatcommand(
"listplayers", {
privs = {},
func = function(user)
local result = table.concat(modpol.list_users(),", ")
return true, "All players: " .. result
end,
})
-- ===================================================================
-- /joinorg
minetest.register_chatcommand(
"joinorg", {
privs = {},
func = function(user, param)
local result = modpol.add_member(param, user)
return true, result
end,
})
-- ===================================================================
-- /pollself [question]
-- asks the user a question specified in param
minetest.register_chatcommand(
"pollself", {
privs = {},
func = function(user, param)
modpol.binary_poll_user(user, param)
return true, result
end,
})
-- ===================================================================
-- Minetest events
-- ===================================================================
-- ===================================================================
-- Receiving fields
minetest.register_on_player_receive_fields(function (player, formname, fields)
-- modpol:poll
@ -258,42 +160,5 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
end
end)
-- /pollself [question]
-- asks the user a question
minetest.register_chatcommand(
"pollself", {
privs = {},
func = function(user, param)
modpol.binary_poll_user(user, param)
return true, result
end,
})
--[[ TKTK need to enable more complex ineractions
- checkboxes, radio
- write-in
]]--
-- MESSAGE FUNCTIONS
function modpol.org_message(org, speaker, message)
-- If org doesn't exit, broadcast to all
-- If org doesn't exist, don't broadcast
-- use: minetest.chat_send_player("player1", "This is a chat message for player1")
end
-- register at chat command for this
-- Add HUD interface that shows status: orgs, privileges https://rubenwardy.com/minetest_modding_book/en/players/hud.html
-- toggle it on and off
--[[ INITIALIZING: post-functions ]]--
-- create instance if not present
if (modpol.orgs["instance"] == nil) then
modpol.new_org("instance", modpol.list_members())
end
-- TKTK does this need to be on_joinplayer? still isn't adding the singleplayer
-- ===================================================================
-- End of file.

25
interactions.lua Normal file
View File

@ -0,0 +1,25 @@
-- ===================================================================
-- /orgs.lua
-- User interaction functions for Modular Politics
-- Called by modpol.lua
-- ===================================================================
-- Function: modpol.binary_poll_user(user, question)
-- Params: user (string), question (string)
-- Output:
-- presents a yes/no/abstain poll to a user, returns answer
modpol.binary_poll_user = function(user, question)
local query = "Poll for " .. user .. " (y/n/a): ".. question
local answer
repeat
print(query)
answer= io.read()
until answer == "y" or answer == "n" or answer == "a"
if answer == "y" then
return "Yes"
elseif answer == "n" then
return "No"
else
return "Abstain"
end
end

View File

@ -1,7 +1,8 @@
name = modpol
depends = default
title = Modular Politics
author = ntnsndr <n@nathanschneider.info>
description = A governance layer API for Minetest
description = A governance layer framework in Lua
license = Hippocratic License
forum = TBA
version = 0.1

77
modpol.lua Normal file
View File

@ -0,0 +1,77 @@
-- ===================================================================
-- /modpol.lua
-- Modular Politics (modpol) core
-- ===================================================================
-- Basic tables
-- Main API table
modpol = {
}
-- Table for all active governance data
modpol.orgs = {
}
-- Record of every state change should appear here
modpol.ledger = {
}
-- ===================================================================
-- Locate framework top-level directory.
-- This function is intended for use under Linux and/or UNIX only. It
-- returns a relative or absolute path for the framework top-level
-- directory without a trailing slash.
local get_script_dir = function()
local str = debug.getinfo (2, "S").source:sub (2)
str = str:match ("(.*/)") or "."
str = str:gsub ("/$", "", 1)
return str
end
-- Absolute or relative path to script directory.
local topdir = get_script_dir()
modpol.topdir = topdir
print (topdir)
-- ===================================================================
-- Load dependencies
-- OldCoder utilities
dofile (topdir .. "/ocutil.lua")
-- ===================================================================
-- Persistent storage
-- must implement modpol.load_storage() and modpol.store_data()
-- Select a storage method
-- Works with CLI:
dofile (topdir .. "/storage-local.lua")
-- Works with Minetest 5:
-- dofile (topdir .. "/storage-mod_storage.lua")
-- If available, load persistent storage into active tables
modpol.load_storage()
-- ===================================================================
-- ModPol core features
dofile (topdir .. "/users.lua")
dofile (topdir .. "/orgs.lua")
dofile (topdir .. "/interactions.lua")
-- messaging functions
-- ===================================================================
-- Final checks
-- create instance if not present
if (modpol.orgs["instance"] == nil) then
modpol.add_org("instance", modpol.list_users())
end
ocutil.log ("modpol loaded")
-- ===================================================================
-- End of file.

743
ocutil.lua Normal file
View File

@ -0,0 +1,743 @@
-- ===================================================================
-- Overview.
-- This file is "top/ocutil.lua".
-- This file provides a collection of largely portable Lua utility
-- functions. The collection is descended from one assembled by Old-
-- Coder (Robert Kiraly).
-- ===================================================================
ocutil = {}
ocutil.log_console = true -- Flag: Copy log to console
ocutil.logdir = nil -- Absolute path for log-file direc-
-- tory (or nil)
-- ===================================================================
-- Function: ocutil.fixnil
-- Params: string s
-- Outputs:
--
-- If the string is nil , returns "(nil)"
-- if the string is empty , returns "(empty)"
-- Otherwise, returns the string as it stands
--
-- This function can be used to clarify -- and/or avoid crashes in --
-- debug messages.
-- ===================================================================
ocutil.fixnil = function (s)
if s == nil then s = "(nil)"
elseif s == "" then s = "(empty)"
end
return s
end
-- ===================================================================
-- Function: ocutil.setlogdir
-- Params: string path
-- Outputs:
--
-- The input string should be the absolute path for a writable direc-
-- tory that already exists. This function tells "ocutil.log" to put
-- log files in that directory.
-- ===================================================================
ocutil.setlogdir = function (path)
if path ~= nil and path ~= "" then
ocutil.logdir = path
end
end
-- ===================================================================
-- Function: ocutil.setlogname
-- Params: string name
-- Outputs:
--
-- The input string should be a filename without a path component.
-- This function tells "ocutil.log" to use the specified filename for
-- the main log file.
--
-- "ocutil.setlogdir" must be called separately to set the log-file
-- directory.
-- ===================================================================
ocutil.setlogname = function (name)
if name ~= nil and name ~= "" then
ocutil.logname = name
end
end
-- ===================================================================
-- Function: ocutil.log
-- Params: string s
-- Outputs:
--
-- Logs the specified string. Appends a newline if not already pre-
-- sent.
-- ===================================================================
ocutil.log = function (s)
s = ocutil.fixnil (s)
s = s:gsub ("\n$", "", 1) -- Remove trailing newline initially
if ocutil.log_console then
print (s)
end
if ocutil.logdir ~= nil and
ocutil.logname ~= nil then
s = s .. "\n" -- Add trailing newline
local logpath = ocutil.logdir .. "/" .. ocutil.logname
ocutil.file_append (logpath, s)
end
end
-- ===================================================================
-- Function: ocutil.fatal_error
-- Params: string s
-- Outputs:
--
-- Logs "Fatal error: " plus the specified string. Appends a newline
-- if not already present. Terminates the program.
-- ===================================================================
ocutil.fatal_error = function (s)
ocutil.log ("Fatal Error: " .. s)
os.exit (1)
return nil -- Shouldn't be reached
end
-- ===================================================================
-- Function: ocutil.panic
-- Params: string s
-- Outputs:
--
-- Logs "Internal error: " plus the specified string. Appends a new-
-- line if not already present. Terminates the program.
-- ===================================================================
ocutil.panic = function (s)
ocutil.log ("Internal Error: " .. s)
os.exit (1)
return nil -- Shouldn't be reached
end
-- ===================================================================
-- Function: ocutil.str_empty
-- Params: string x
-- Outputs:
--
-- Returns true if the string is nil or empty
-- Returns false otherwise
-- ===================================================================
ocutil.str_empty = function (x)
if x == nil or x == "" then
return true
else
return false
end
end
-- ===================================================================
-- Function: ocutil.str_nonempty
-- Params: string x
-- Outputs:
--
-- Returns true if the string is nil or empty
-- Returns false otherwise
-- ===================================================================
ocutil.str_nonempty = function (x)
if x == nil or x == "" then
return false
else
return true
end
end
-- ===================================================================
-- Function: ocutil.table_empty
-- Params: table tab
-- Outputs:
--
-- Returns true if the table is empty
-- Returns false otherwise
-- ===================================================================
ocutil.table_empty = function (tab)
local next = next
if next (tab) == nil then return true end
return false
end
-- ===================================================================
-- Function: ocutil.table_nonempty
-- Params: table tab
-- Outputs:
--
-- Returns false if the table is empty
-- Returns true otherwise
-- ===================================================================
ocutil.table_nonempty = function (tab)
if ocutil.table_empty (tab) then return false end
return true
end
-- ===================================================================
-- Function: ocutil.string_contains
-- Params: strings a, b
-- Outputs:
--
-- Returns true if the 1st string contains the 2nd one
-- Returns false otherwise
-- ===================================================================
ocutil.str_contains = function (a, b)
if string.match (a, b) then
return true
else
return false
end
end
-- ===================================================================
-- Function: ocutil.str_false
-- Params: string x
-- Outputs:
--
-- This function checks the string for an explicit false (or equiva-
-- lent) setting (as opposed to empty or nil). If such a setting is
-- found, true is returned. Otherwise, false is returned.
-- ===================================================================
ocutil.str_false = function (x)
if x == "false" or x == "no" or
x == "off" or x == "0" or
x == false or x == 0 then
return true
else
return false
end
end
-- ===================================================================
-- Function: ocutil.str_true
-- Params: string x
-- Outputs:
--
-- This function checks the string for an explicit true (or equiva-
-- lent) setting. If such a setting is found, true is returned. Other-
-- wise, false is returned.
-- ===================================================================
ocutil.str_true = function (x)
if x == "true" or x == "yes" or
x == "on" or x == "1" or
x == true or x == 1 then
return true
else
return false
end
end
-- ===================================================================
-- Function: ocutil.starts_with
-- Params: strings String, Start
-- Outputs:
--
-- Returns true if the 1st string starts with the 2nd one
-- Returns false otherwise
-- ===================================================================
ocutil.starts_with = function (String, Start)
if string.sub (String, 1, string.len (Start)) == Start then
return true
else
return false
end
end
-- ===================================================================
-- Function: ocutil.not_starts_with
-- Params: strings String, Start
-- Outputs:
--
-- Returns false if the 1st string starts with the 2nd one
-- Returns true otherwise
-- ===================================================================
ocutil.not_starts_with = function (String, Start)
if string.sub (String, 1, string.len (Start)) == Start then
return false
else
return true
end
end
-- ===================================================================
-- Function: ocutil.ends_with
-- Params: strings String, End
-- Outputs:
--
-- Returns true if the 1st string ends with the 2nd one
-- Returns false otherwise
-- As a special case, returns true if the 2nd string is empty
-- ===================================================================
ocutil.ends_with = function (String, End)
return End == '' or string.sub (String,
-string.len (End)) == End
end
-- ===================================================================
-- Function: ocutil.not_ends_with
-- Params: strings String, End
-- Outputs:
-- Returns false if the 1st string ends with the 2nd one
-- Returns true otherwise
-- As a special case, returns false if the 2nd string is empty
-- ===================================================================
ocutil.not_ends_with = function (String, End)
if ocutil.ends_with (String, End) then
return false
else
return true
end
end
-- ===================================================================
-- Function: ocutil.firstch
-- Params: string str
-- Outputs:
-- Returns the 1st character of the string
-- ===================================================================
ocutil.firstch = function (str)
return string.sub (str, 1, 1)
end
-- ===================================================================
-- Function: ocutil.first_to_upper
-- Params: string str
-- Outputs:
-- Returns the 1st character of the string in upper case
-- ===================================================================
ocutil.first_to_upper = function (str)
return (str:gsub ("^%l", string.upper))
end
-- ===================================================================
-- Function: ocutil.all_first_to_upper
-- Params: string str, flag cvtspace
-- Outputs:
--
-- Converts the 1st character of each word in the string to upper
-- case and returns the result. Words are delimited by spaces or un-
-- derscores. If the flag is true, also replaces spaces with under-
-- scores.
-- ===================================================================
ocutil.all_first_to_upper = function (str, cvtspace)
str = str:gsub ("^%l", string.upper)
str = str:gsub ("[_ ]%l",
function (a) return string.upper (a) end)
if ocutil.str_true (cvtspace) then
str = str:gsub ("_", " ")
end
return (str)
end
-- ===================================================================
-- Function: ocutil.strtok
-- Params: strings source, delimitch
-- Outputs:
--
-- Breaks the source string up into a list of words and returns the
-- list.
--
-- If "delimitch" is omitted, nil, or empty, words are delimited by
-- spaces. Otherwise, words are delimited by any of the characters in
-- "delimitch".
-- ===================================================================
ocutil.strtok = function (source, delimitch)
if delimitch == nil or
delimitch == "" then delimitch = " " end
local parts = {}
local pattern = '([^' .. delimitch .. ']+)'
string.gsub (source, pattern,
function (value)
value = value:gsub ("^ *", "")
value = value:gsub (" *$", "")
parts [#parts + 1] = value
end)
return parts
end
-- ===================================================================
-- Function: ocutil.swap_key_value
-- Params: table itable
-- Outputs:
-- Turns keys into values and vice versa and returns the resulting
-- table.
-- ===================================================================
ocutil.swap_key_value = function (itable)
if itable == nil then return nil end
local otable = {}
for key, value in pairs (itable) do otable [value] = key end
return otable
end
-- ===================================================================
-- Function: ocutil.ktable_to_vtable
-- Params: table ktable
-- Outputs:
--
-- "ktable_to_vtable" is short for "convert key table to value table".
--
-- The input table is assumed to be an arbitrary key-based list of the
-- general form:
-- { dog="woof", apple="red", book="read" }
--
-- This function takes the keys and returns them as an integer-indexed
-- array with the former keys stored now as values. The array is sort-
-- ed based on the former keys. The original values in the input table
-- are discarded. Sample output:
--
-- { "apple", "book", "dog", ... }
-- Here's a complete code fragment which demonstrates operation of
-- this function:
-- local tabby = { dog="woof", apple="red", book="read" }
--
-- print ("\nInput table:")
-- for ii, value in pairs (tabby) do
-- print (ii .. ") " .. tabby [ii])
-- end
--
-- tabby = ocutil.ktable_to_vtable (tabby)
--
-- print ("\nOutput table:")
-- for ii, value in ipairs (tabby) do
-- print (ii .. ") " .. tabby [ii])
-- end
-- ===================================================================
ocutil.ktable_to_vtable = function (ktable)
local vtable = {}
if ktable == nil then return vtable end
for key, _ in pairs (ktable) do
table.insert (vtable, key)
end
table.sort (vtable)
return vtable
end
-- ===================================================================
-- Function: ocutil.vtable_to_ktable
-- Params: table vtable, scalar set_to
-- Outputs:
--
-- "vtable_to_ktable" is short for "convert value table to key table".
--
-- The input table is assumed to be an integer-indexed array of scalar
-- values.
--
-- This function creates a general table that holds the scalar values
-- stored now as keys and returns the new table. The new table maps
-- each of the keys to the value specified by "set_to".
--
-- If "set_to" is omitted or nil, it defaults to boolean true.
-- Here's a complete code fragment which demonstrates operation of
-- this function:
--
-- local tabby = { "apple", "book", "dog" }
--
-- print ("\nInput table:")
-- for ii, value in ipairs (tabby) do
-- print (ii .. ") " .. tabby [ii])
-- end
--
-- tabby = ocutil.vtable_to_ktable (tabby, 42)
--
-- print ("\nOutput table:")
-- for ii, value in pairs (tabby) do
-- print (ii .. ") " .. tabby [ii])
-- end
-- ===================================================================
ocutil.vtable_to_ktable = function (vtable, set_to)
local ktable = {}
if vtable == nil then return ktable end
if set_to == nil then set_to = true end
for _, value in pairs (vtable) do
ktable [value] = set_to
end
return ktable
end
-- ===================================================================
-- Function: ocutil.make_set
-- Params: array harry
-- Outputs:
--
-- This function makes a set out of an array. Specifically, it returns
-- a new table with the array's values as keys. The new table maps
-- each key to boolean true.
--
-- Here's a complete code fragment which demonstrates operation of
-- this function:
--
-- local color_set = ocutil.make_set { "red", "green", "blue" }
-- if color_set ["red"] ~= nil then print ("Supported color") end
-- ===================================================================
ocutil.make_set = function (list)
local set = {}
for _, l in ipairs (list) do set [l] = true end
return set
end
-- ===================================================================
-- Function: ocutil.pos_to_str
-- Params: position-table pos
-- Outputs:
--
-- This function takes a table of the following form as input:
-- { x=25.0, y=-135.5, z=2750.0 }
--
-- It returns the x-y-z values, separated by commas, as a string.
-- ===================================================================
ocutil.pos_to_str = function (pos)
return pos.x .. "," .. pos.y .. "," .. pos.z
end
-- ===================================================================
-- Function: ocutil.table_length
-- Params: table tabby
-- Outputs: Returns number of entries in table
--
-- Note: This function works for tables in general as well as for int-
-- eger-indexed tables (i.e., arrays).
-- ===================================================================
ocutil.table_length = function (tabby)
local count = 0
for _ in pairs (tabby) do count = count+1 end
return count
end
-- ===================================================================
-- Function: ocutil.clone_table
-- Params: table tabby
-- Outputs:
--
-- This function returns an independent clone of the input table. It
-- should work correctly for most cases, but special cases that re-
-- quire additional work may exist.
--
-- This function may also be called as "ocutil.table_clone".
-- ===================================================================
ocutil.clone_table = function (tabby)
if tabby == nil then return nil end
local copy = {}
for k, v in pairs (tabby) do
if type (v) == 'table' then v = ocutil.clone_table (v) end
copy [k] = v
end
return copy
end
ocutil.table_clone = ocutil.clone_table
-- ===================================================================
-- Function: ocutil.file_exists
-- Params: string path
-- Outputs:
--
-- The input string should be the relative or absolute pathname for a
-- data file. If the file is read-accessible, this function returns
-- true. Otherwise, it returns false.
-- ===================================================================
ocutil.file_exists = function (path)
local file, err
file, err = io.open (path, "rb")
if err then return false end
file:close()
return true
end
-- ===================================================================
-- Function: ocutil.file_missing
-- Params: string path
-- Outputs:
--
-- The input string should be the relative or absolute pathname for a
-- data file. If the file is read-accessible, this function returns
-- false. Otherwise, it returns true.
-- ===================================================================
ocutil.file_missing = function (path)
if ocutil.file_exists (path) then return false end
return true
end
-- ===================================================================
-- Function: ocutil.file_read
-- Params: string path
-- Outputs:
--
-- The input string should be the absolute pathname for a data file.
-- If the file is read-accessible, this function returns the contents
-- of the file. Otherwise, it returns nil.
--
-- Warning: There is presently no check for a maximum file size.
-- ===================================================================
ocutil.file_read = function (path)
local file, err
file, err = io.open (path, "rb")
if err then return nil end
local data = file:read ("*a")
file:close()
return data
end
-- ===================================================================
-- Function: ocutil.file_write
-- Params: strings path, data
-- Outputs:
--
-- The 1st string should be the absolute pathname for a data file. The
-- file doesn't need to exist initially but the directory that will
-- contain it does need to exist.
--
-- This function writes the 2nd string to the file. Existing stored
-- data is discarded.
--
-- This function returns true if successful and false otherwise.
-- ===================================================================
ocutil.file_write = function (path, data)
local file, err
file, err = io.open (path, "wb")
if err then return false end
io.output (file)
io.write (data)
io.close (file)
return true
end
-- ===================================================================
-- Function: ocutil.file_append
-- Params: strings path, data
-- Outputs:
--
-- This function is identical to "ocutil.file_write" except for one
-- difference: It appends to existing files as opposed to overwrites
-- them.
-- ===================================================================
ocutil.file_append = function (path, data)
local file, err
file, err = io.open (path, "ab")
if err then return false end
io.output (file)
io.write (data)
io.close (file)
return true
end
-- ===================================================================
-- End of file.

192
orgs.lua Normal file
View File

@ -0,0 +1,192 @@
-- ===================================================================
-- /orgs.lua
-- Org-related functions for Modular Politics
-- Called by modpol.lua
-- ===================================================================
-- Function: modpol.record
-- Params: strings msg, org
-- Outputs:
--
-- "msg" specifies an event and/or status message.
-- "org" specifies an "org" name.
--
-- This function adds the message to a global ledger and, if "org"
-- specifies a valid "org", to an "org"-specific ledger. Both the mem-
-- ory-resident and on-disk copies of the data structures used are up-
-- dated.
modpol.record = function (org_name, msg)
-- Record in global ledger
table.insert (modpol.ledger, msg)
-- Record in "org"-specific ledger
if modpol.orgs [org_name] ~= nil then
local org_ledger = modpol.orgs [org_name]["ledger"]
if org_ledger == nil then
modpol.orgs [org_name]["ledger"] = { msg }
else
modpol.orgs [org_name]["ledger"] =
table.insert (org_ledger, msg)
end
end
modpol.store_data() -- Copy data to disk
end
-- ===================================================================
-- Function: modpol.add_org
-- Params: string name, table members
-- Output:
--
-- This function creates an "org". It returns either an error message
-- starting with "-!-" or a success message.
--
-- The string parameter specifies the "org" name.
--
-- The specified table should be an integer-indexed array of member
-- names.
modpol.add_org = function (org_name, members)
local str
if ocutil.str_empty (org_name) then
return "Error: Org needs a name"
end
ocutil.log ("Adding org " .. org_name)
if modpol.orgs [org_name] ~= nil then
str = "Error: Org " .. org_name .. " already exists"
ocutil.log (str)
return str
end
modpol.orgs [org_name] = { members=members }
local msg = "New org: " .. org_name ..
" (" .. table.concat (members, ", ") .. ")"
modpol.record (org_name, msg)
return msg
end
-- ===================================================================
-- Function: modpol.list_orgs
-- Params: None
-- Output:
-- This function returns a text-format list of "orgs". The list shows
-- one "org" per line in the following format:
-- org_name (member, member, ...)
-- If there are no "orgs", the output is an empty string.
modpol.list_orgs = function()
local outbuf = ""
local str
for org_name, org_data in pairs (modpol.orgs) do
-- Process next "org"
-- Build string version of member list
local memcat = org_data ["members"]
if ocutil.str_empty (memcat) then
memcat = "(empty)"
else
memcat = "(" .. table.concat (memcat, ", ") .. ")"
end
-- Build output string
outbuf = outbuf .. org_name .. " " .. memcat .. "\n"
end
return outbuf
end
-- ===================================================================
-- Function: modpol.reset_orgs
-- Params: None
-- Removes all orgs and recreates blank org "instance" with all
-- current users
-- returns confirmation message
modpol.reset_orgs = function()
local users = list_users()
modpol.orgs = {}
local message = "Orgs purged"
modpol.record(nil, message)
modpol.add_org("instance", users)
return message
end
-- ===================================================================
-- Function: modpol.add_member
-- Params: org (string), member (string)
-- Output:
-- Adds the specified member to the specified org
-- Returns confirmation or error message
modpol.add_member = function(org, member)
if (modpol.orgs[org] == nil) then
return "Error: No such org"
else
table.insert(modpol.orgs[org]["members"], member)
local message = member .. " added to org " .. org
modpol.record(message, org)
return message
end
end
-- ===================================================================
-- Function: modpol.is_member
-- Params: org (string), member (string)
-- Output: Boolean depending on membership or nil/error message
modpol.is_member = function(org, member)
if (modpol.orgs[org] == nil) then
return nil, "Error: No such org"
else
local member_table = modpol.orgs[org]["members"]
if member_table then
for index, value in pairs(member_table) do
if value == member then
-- match found
return true
end
end
-- no match found
return false
end
return false
end
end
-- ===================================================================
-- Function: modpol.remove_member
-- Params: org (string), member (string)
-- Output:
-- Removes the specified member from the specified org
-- Returns confirmation or error message
function modpol.remove_member(org, member)
local message = "Error: No such org"
if (modpol.orgs[org] == nil) then
return nil, message
else
local member_table = modpol.orgs[org]["members"]
if member_table then
for index, value in pairs(member_table) do
if value == member then
-- match found, set to nil
value = nil
message = member .. " removed from org " .. org
return message
end
end
end
-- no match found (or org has no members)
message = member .. " not found in org " .. org
return nil, message
end
end
-- ===================================================================
-- TKTK:
-- + rename_org(old_name, new_name)
-- +
-- ===================================================================
-- End of file.

21
serpent/LICENSE.txt Normal file
View File

@ -0,0 +1,21 @@
Serpent source is released under the MIT License
Copyright (c) 2012-2018 Paul Kulchenko (paul@kulchenko.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

293
serpent/README.md Normal file
View File

@ -0,0 +1,293 @@
# Serpent
Lua serializer and pretty printer.
## Features
* Human readable:
* Provides single-line and multi-line output.
* Nested tables are properly indented in the multi-line output.
* Numerical keys are listed first.
* Keys are (optionally) sorted alphanumerically.
* Array part skips keys (`{'a', 'b'}` instead of `{[1] = 'a', [2] = 'b'}`).
* `nil` values are included when expected (`{1, nil, 3}` instead of `{1, [3]=3}`).
* Keys use short notation (`{foo = 'foo'}` instead of `{['foo'] = 'foo'}`).
* Shared references and self-references are marked in the output.
* Machine readable: provides reliable deserialization using `loadstring()`.
* Supports deeply nested tables.
* Supports tables with self-references.
* Keeps shared tables and functions shared after de/serialization.
* Supports function serialization using `string.dump()`.
* Supports serialization of global functions.
* Supports `__tostring` and `__serialize` metamethods.
* Escapes new-line `\010` and end-of-file control `\026` characters in strings.
* Configurable with options and custom formatters.
## Usage
```lua
local serpent = require("serpent")
local a = {1, nil, 3, x=1, ['true'] = 2, [not true]=3}
a[a] = a -- self-reference with a table as key and value
print(serpent.dump(a)) -- full serialization
print(serpent.line(a)) -- single line, no self-ref section
print(serpent.block(a)) -- multi-line indented, no self-ref section
local fun, err = loadstring(serpent.dump(a))
if err then error(err) end
local copy = fun()
-- or using serpent.load:
local ok, copy = serpent.load(serpent.dump(a))
print(ok and copy[3] == a[3])
```
## Functions
Serpent provides three functions that are shortcuts to the same
internal function, but set different options by default:
* `dump(a[, {...}])` -- full serialization; sets `name`, `compact` and `sparse` options;
* `line(a[, {...}])` -- single line pretty printing, no self-ref section; sets `sortkeys` and `comment` options;
* `block(a[, {...}])` -- multi-line indented pretty printing, no self-ref section; sets `indent`, `sortkeys`, and `comment` options.
Note that `line` and `block` functions return pretty-printed data structures and if you want to deserialize them, you need to add `return` before running them through `loadstring`.
For example: `loadstring('return '..require('mobdebug').line("foo"))() == "foo"`.
While you can use `loadstring` or `load` functions to load serialized fragments, Serpent also provides `load` function that adds safety checks and reports an error if there is any executable code in the fragment.
* `ok, res = serpent.load(str[, {safe = true}])` -- loads serialized fragment; you need to pass `{safe = false}` as the second value if you want to turn safety checks off.
Similar to `pcall` and `loadstring` calls, `load` returns status as the first value and the result or the error message as the second value.
## Options
* indent (string) -- indentation; triggers long multi-line output.
* comment (true/false/maxlevel) -- provide stringified value in a comment (up to `maxlevel` of depth).
* sortkeys (true/false/function) -- sort keys.
* sparse (true/false) -- force sparse encoding (no nil filling based on `#t`).
* compact (true/false) -- remove spaces.
* fatal (true/False) -- raise fatal error on non-serilizable values.
* nocode (true/False) -- disable bytecode serialization for easy comparison.
* nohuge (true/False) -- disable checking numbers against undefined and huge values.
* maxlevel (number) -- specify max level up to which to expand nested tables.
* maxnum (number) -- specify max number of elements in a table.
* maxlength (number) -- specify max length for all table elements.
* metatostring (True/false) -- use `__tostring` metamethod when serializing tables (**v0.29**);
set to `false` to disable and serialize the table as is, even when `__tostring` is present.
* numformat (string; "%.17g") -- specify format for numeric values as shortest possible round-trippable double (**v0.30**).
Use "%.16g" for better readability and "%.17g" (the default value) to preserve floating point precision.
* valignore (table) -- allows to specify a list of values to ignore (as keys).
* keyallow (table) -- allows to specify the list of keys to be serialized.
Any keys not in this list are not included in final output (as keys).
* keyignore (table) -- allows to specity the list of keys to ignore in serialization.
* valtypeignore (table) -- allows to specify a list of value *types* to ignore (as keys).
* custom (function) -- provide custom output for tables.
* name (string) -- name; triggers full serialization with self-ref section.
These options can be provided as a second parameter to Serpent functions.
```lua
block(a, {fatal = true})
line(a, {nocode = true, valignore = {[arrayToIgnore] = true}})
function todiff(a) return dump(a, {nocode = true, indent = ' '}) end
```
Serpent functions set these options to different default values:
* `dump` sets `compact` and `sparse` to `true`;
* `line` sets `sortkeys` and `comment` to `true`;
* `block` sets `sortkeys` and `comment` to `true` and `indent` to `' '`.
## Metatables with __tostring and __serialize methods
If a table or a userdata value has `__tostring` or `__serialize` method, the method will be used to serialize the value.
If `__serialize` method is present, it will be called with the value as a parameter.
if `__serialize` method is not present, but `__tostring` is, then `tostring` will be called with the value as a parameter.
In both cases, the result will be serialized, so `__serialize` method can return a table, that will be serialize and replace the original value.
## Sorting
A custom sort function can be provided to sort the contents of tables. The function takes 2 parameters, the first being the table (a list) with the keys, the second the original table. It should modify the first table in-place, and return nothing.
For example, the following call will apply a sort function identical to the standard sort, except that it will not distinguish between lower- and uppercase.
```lua
local mysort = function(k, o) -- k=keys, o=original table
local maxn, to = 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..maxn.."d"):format(d) end
local sort = function(a,b)
-- this -vvvvvvvvvv- is needed to sort array keys first
return ((k[a] and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))):upper()
< ((k[b] and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum))):upper()
end
table.sort(k, sort)
end
local content = { some = 1, input = 2, To = 3, serialize = 4 }
local result = require('serpent').block(content, {sortkeys = mysort})
```
## Formatters
Serpent supports a way to provide a custom formatter that allows to fully
customize the output. The formatter takes four values:
* tag -- the name of the current element with '=' or an empty string in case of array index,
* head -- an opening table bracket `{` and associated indentation and newline (if any),
* body -- table elements concatenated into a string using commas and indentation/newlines (if any),
* tail -- a closing table bracket `}` and associated indentation and newline (if any), and
* level -- the current level.
For example, the following call will apply
`Foo{bar} notation to its output (used by Metalua to display ASTs):
```lua
print((require "serpent").block(ast, {comment = false, custom =
function(tag,head,body,tail)
local out = head..body..tail
if tag:find('^lineinfo') then
out = out:gsub("\n%s+", "") -- collapse lineinfo to one line
elseif tag == '' then
body = body:gsub('%s*lineinfo = [^\n]+', '')
local _,_,atag = body:find('tag = "(%w+)"%s*$')
if atag then
out = "`"..atag..head.. body:gsub('%s*tag = "%w+"%s*$', '')..tail
out = out:gsub("\n%s+", ""):gsub(",}","}")
else out = head..body..tail end
end
return tag..out
end}))
```
## Limitations
* Doesn't handle userdata (except filehandles in `io.*` table).
* Threads, function upvalues/environments, and metatables are not serialized.
## Performance
A simple performance test against `serialize.lua` from metalua, `pretty.write`
from Penlight, and `tserialize.lua` from lua-nucleo is included in `t/bench.lua`.
These are the results from one of the runs:
* nucleo (1000): 0.256s
* metalua (1000): 0.177s
* serpent (1000): 0.22s
* serpent (1000): 0.161s -- no comments, no string escapes, no math.huge check
* penlight (1000): 0.132s
Serpent does additional processing to escape `\010` and `\026` characters in
strings (to address http://lua-users.org/lists/lua-l/2007-07/msg00362.html,
which is already fixed in Lua 5.2) and to check all numbers for `math.huge`.
The seconds number excludes this processing to put it on an equal footing
with other modules that skip these checks (`nucleo` still checks for `math.huge`).
## Author
Paul Kulchenko (paul@kulchenko.com)
## License
See LICENSE file.
## History
### v0.30 (Sep 01 2017)
- Updated `pairs` to avoid using `__pairs` in Lua 5.2+.
- Added `metatostring` option to disable `__tostring` processing during serialization.
- Added `level` to the parameters of the custom function (closes #25).
- Added `maxlength` option to limit the space taken by table elements.
- Optimized serialization of functions when `nocode` option is specified.
- Added protection from `__serialize` check failing (pkulchenko/ZeroBraneStudio#732).
- Added `keyignore` option for the serializer.
- Added check for environments that may not have 'default' tables as tables.
- Added numeric format to preserve floating point precision (closes #17).
- Added using `debug.metatable` when available.
- Improved handling of failures in `__tostring` (pkulchenko/ZeroBraneStudio#446).
### v0.28 (May 06 2015)
- Switched to a method proposed by @SoniEx2 to disallow function calls (#15).
- Added more `tostring` for Lua 5.3 support (pkulchenko/ZeroBraneStudio#401).
- Updated environment handling to localize the impact (#15).
- Added setting env to protect against assigning global functions (closes #15).
- Updated tests to work with Lua 5.3.
- Added explicit `tostring` for Lua 5.3 with `LUA_NOCVTN2S` set (pkulchenko/ZeroBraneStudio#401).
- Fixed crash when not all Lua standard libraries are loaded (thanks to Tommy Nguyen).
- Improved Lua 5.2 support for serialized functions.
### v0.27 (Jan 11 2014)
- Fixed order of elements in the array part with `sortkeys=true` (fixes #13).
- Updated custom formatter documentation (closes #11).
- Added `load` function to deserialize; updated documentation (closes #9).
### v0.26 (Nov 05 2013)
- Added `load` function that (safely) loads serialized/pretty-printed values.
- Updated documentation.
### v0.25 (Sep 29 2013)
- Added `maxnum` option to limit the number of elements in tables.
- Optimized processing of tables with numeric indexes.
### v0.24 (Jun 12 2013)
- Fixed an issue with missing numerical keys (fixes #8).
- Fixed an issue with luaffi that returns `getmetatable(ffi.C)` as `true`.
### v0.23 (Mar 24 2013)
- Added support for `cdata` type in LuaJIT (thanks to [Evan](https://github.com/neomantra)).
- Added comment to indicate incomplete output.
- Added support for metatables with __serialize method.
- Added handling of metatables with __tostring method.
- Fixed an issue with having too many locals in self-reference section.
- Fixed emitting premature circular reference in self-reference section, which caused invalid serialization.
- Modified the sort function signature to also pass the original table, so not only keys are available when sorting, but also the values in the original table.
### v0.22 (Jan 15 2013)
- Added ability to process __tostring results that may be non-string values.
### v0.21 (Jan 08 2013)
- Added `keyallow` and `valtypeignore` options (thanks to Jess Telford).
- Renamed `ignore` to `valignore`.
### v0.19 (Nov 16 2012)
- Fixed an issue with serializing shared functions as keys.
- Added serialization of metatables using __tostring (when present).
### v0.18 (Sep 13 2012)
- Fixed an issue with serializing data structures with circular references that require emitting temporary variables.
- Fixed an issue with serializing keys pointing to shared references.
- Improved overall serialization logic to inline values when possible.
### v0.17 (Sep 12 2012)
- Fixed an issue with serializing userdata that doesn't provide tostring().
### v0.16 (Aug 28 2012)
- Removed confusing --[[err]] comment from serialized results.
- Added a short comment to serialized functions when the body is skipped.
### v0.15 (Jun 17 2012)
- Added `ignore` option to allow ignoring table values.
- Added `comment=num` option to set the max level up to which add comments.
- Changed all comments (except math.huge) to be controlled by `comment` option.
### v0.14 (Jun 13 2012)
- Fixed an issue with string keys with numeric values `['3']` getting mixed
with real numeric keys (only with `sortkeys` option set to `true`).
- Fixed an issue with negative and real value numeric keys being misplaced.
### v0.13 (Jun 13 2012)
- Added `maxlevel` option.
- Fixed key sorting such that `true` and `'true'` are always sorted in
the same order (for a more stable output).
- Removed addresses from names of temporary variables (for stable output).
### v0.12 (Jun 12 2012)
- Added options to configure serialization process.
- Added `goto` to the list of keywords for Lua 5.2.
- Changed interface to dump/line/block methods.
- Changed `math.huge` to 1/0 for better portability.
- Replaced \010 with \n for better readability.
### v0.10 (Jun 03 2012)
- First public release.

BIN
serpent/serpent-git.tar.bz2 Normal file

Binary file not shown.

148
serpent/serpent.lua Normal file
View File

@ -0,0 +1,148 @@
local n, v = "serpent", "0.302" -- (C) 2012-18 Paul Kulchenko; MIT License
local c, d = "Paul Kulchenko", "Lua serializer and pretty printer"
local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'}
local badtype = {thread = true, userdata = true, cdata = true}
local getmetatable = debug and debug.getmetatable or getmetatable
local pairs = function(t) return next, t end -- avoid using __pairs in Lua 5.2+
local keyword, globals, G = {}, {}, (_G or _ENV)
for _,k in ipairs({'and', 'break', 'do', 'else', 'elseif', 'end', 'false',
'for', 'function', 'goto', 'if', 'in', 'local', 'nil', 'not', 'or', 'repeat',
'return', 'then', 'true', 'until', 'while'}) do keyword[k] = true end
for k,v in pairs(G) do globals[v] = k end -- build func to name mapping
for _,g in ipairs({'coroutine', 'debug', 'io', 'math', 'string', 'table', 'os'}) do
for k,v in pairs(type(G[g]) == 'table' and G[g] or {}) do globals[v] = g..'.'..k end end
local function s(t, opts)
local name, indent, fatal, maxnum = opts.name, opts.indent, opts.fatal, opts.maxnum
local sparse, custom, huge = opts.sparse, opts.custom, not opts.nohuge
local space, maxl = (opts.compact and '' or ' '), (opts.maxlevel or math.huge)
local maxlen, metatostring = tonumber(opts.maxlength), opts.metatostring
local iname, comm = '_'..(name or ''), opts.comment and (tonumber(opts.comment) or math.huge)
local numformat = opts.numformat or "%.17g"
local seen, sref, syms, symn = {}, {'local '..iname..'={}'}, {}, 0
local function gensym(val) return '_'..(tostring(tostring(val)):gsub("[^%w]",""):gsub("(%d%w+)",
-- tostring(val) is needed because __tostring may return a non-string value
function(s) if not syms[s] then symn = symn+1; syms[s] = symn end return tostring(syms[s]) end)) end
local function safestr(s) return type(s) == "number" and tostring(huge and snum[tostring(s)] or numformat:format(s))
or type(s) ~= "string" and tostring(s) -- escape NEWLINE/010 and EOF/026
or ("%q"):format(s):gsub("\010","n"):gsub("\026","\\026") end
local function comment(s,l) return comm and (l or 0) < comm and ' --[['..select(2, pcall(tostring, s))..']]' or '' end
local function globerr(s,l) return globals[s] and globals[s]..comment(s,l) or not fatal
and safestr(select(2, pcall(tostring, s))) or error("Can't serialize "..tostring(s)) end
local function safename(path, name) -- generates foo.bar, foo[3], or foo['b a r']
local n = name == nil and '' or name
local plain = type(n) == "string" and n:match("^[%l%u_][%w_]*$") and not keyword[n]
local safe = plain and n or '['..safestr(n)..']'
return (path or '')..(plain and path and '.' or '')..safe, safe end
local alphanumsort = type(opts.sortkeys) == 'function' and opts.sortkeys or function(k, o, n) -- k=keys, o=originaltable, n=padding
local maxn, to = tonumber(n) or 12, {number = 'a', string = 'b'}
local function padnum(d) return ("%0"..tostring(maxn).."d"):format(tonumber(d)) end
table.sort(k, function(a,b)
-- sort numeric keys first: k[key] is not nil for numerical keys
return (k[a] ~= nil and 0 or to[type(a)] or 'z')..(tostring(a):gsub("%d+",padnum))
< (k[b] ~= nil and 0 or to[type(b)] or 'z')..(tostring(b):gsub("%d+",padnum)) end) end
local function val2str(t, name, indent, insref, path, plainindex, level)
local ttype, level, mt = type(t), (level or 0), getmetatable(t)
local spath, sname = safename(path, name)
local tag = plainindex and
((type(name) == "number") and '' or name..space..'='..space) or
(name ~= nil and sname..space..'='..space or '')
if seen[t] then -- already seen this element
sref[#sref+1] = spath..space..'='..space..seen[t]
return tag..'nil'..comment('ref', level) end
-- protect from those cases where __tostring may fail
if type(mt) == 'table' and metatostring ~= false then
local to, tr = pcall(function() return mt.__tostring(t) end)
local so, sr = pcall(function() return mt.__serialize(t) end)
if (to or so) then -- knows how to serialize itself
seen[t] = insref or spath
t = so and sr or tr
ttype = type(t)
end -- new value falls through to be serialized
end
if ttype == "table" then
if level >= maxl then return tag..'{}'..comment('maxlvl', level) end
seen[t] = insref or spath
if next(t) == nil then return tag..'{}'..comment(t, level) end -- table empty
if maxlen and maxlen < 0 then return tag..'{}'..comment('maxlen', level) end
local maxn, o, out = math.min(#t, maxnum or #t), {}, {}
for key = 1, maxn do o[key] = key end
if not maxnum or #o < maxnum then
local n = #o -- n = n + 1; o[n] is much faster than o[#o+1] on large tables
for key in pairs(t) do if o[key] ~= key then n = n + 1; o[n] = key end end end
if maxnum and #o > maxnum then o[maxnum+1] = nil end
if opts.sortkeys and #o > maxn then alphanumsort(o, t, opts.sortkeys) end
local sparse = sparse and #o > maxn -- disable sparsness if only numeric keys (shorter output)
for n, key in ipairs(o) do
local value, ktype, plainindex = t[key], type(key), n <= maxn and not sparse
if opts.valignore and opts.valignore[value] -- skip ignored values; do nothing
or opts.keyallow and not opts.keyallow[key]
or opts.keyignore and opts.keyignore[key]
or opts.valtypeignore and opts.valtypeignore[type(value)] -- skipping ignored value types
or sparse and value == nil then -- skipping nils; do nothing
elseif ktype == 'table' or ktype == 'function' or badtype[ktype] then
if not seen[key] and not globals[key] then
sref[#sref+1] = 'placeholder'
local sname = safename(iname, gensym(key)) -- iname is table for local variables
sref[#sref] = val2str(key,sname,indent,sname,iname,true) end
sref[#sref+1] = 'placeholder'
local path = seen[t]..'['..tostring(seen[key] or globals[key] or gensym(key))..']'
sref[#sref] = path..space..'='..space..tostring(seen[value] or val2str(value,nil,indent,path))
else
out[#out+1] = val2str(value,key,indent,nil,seen[t],plainindex,level+1)
if maxlen then
maxlen = maxlen - #out[#out]
if maxlen < 0 then break end
end
end
end
local prefix = string.rep(indent or '', level)
local head = indent and '{\n'..prefix..indent or '{'
local body = table.concat(out, ','..(indent and '\n'..prefix..indent or space))
local tail = indent and "\n"..prefix..'}' or '}'
return (custom and custom(tag,head,body,tail,level) or tag..head..body..tail)..comment(t, level)
elseif badtype[ttype] then
seen[t] = insref or spath
return tag..globerr(t, level)
elseif ttype == 'function' then
seen[t] = insref or spath
if opts.nocode then return tag.."function() --[[..skipped..]] end"..comment(t, level) end
local ok, res = pcall(string.dump, t)
local func = ok and "((loadstring or load)("..safestr(res)..",'@serialized'))"..comment(t, level)
return tag..(func or globerr(t, level))
else return tag..safestr(t) end -- handle all other types
end
local sepr = indent and "\n" or ";"..space
local body = val2str(t, name, indent) -- this call also populates sref
local tail = #sref>1 and table.concat(sref, sepr)..sepr or ''
local warn = opts.comment and #sref>1 and space.."--[[incomplete output with shared/self-references skipped]]" or ''
return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end"
end
local function deserialize(data, opts)
local env = (opts and opts.safe == false) and G
or setmetatable({}, {
__index = function(t,k) return t end,
__call = function(t,...) error("cannot call functions") end
})
local f, res = (loadstring or load)('return '..data, nil, nil, env)
if not f then f, res = (loadstring or load)(data, nil, nil, env) end
if not f then return f, res end
if setfenv then setfenv(f, env) end
return pcall(f)
end
local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end
if false then
return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s,
load = deserialize,
dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end,
line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end,
block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end }
end
modpol.serpent.load = deserialize
modpol.serpent.dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end
modpol.serpent.line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end
modpol.serpent.block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end

117
storage-local.lua Normal file
View File

@ -0,0 +1,117 @@
-- ===================================================================
-- /storage-local.lua
-- Persistent storage in /data using Serpent Serializer
-- Unix systems only
-- ===================================================================
-- Set directories and filenames
modpol.datadir = modpol.topdir .. "/data"
modpol.file_ledger = modpol.datadir .. "/ledger.dat"
modpol.file_orgs = modpol.datadir .. "/orgs.dat"
os.execute ("mkdir -p " .. modpol.datadir)
ocutil.setlogdir (modpol.datadir)
ocutil.setlogname ("modpol.log")
-- ===================================================================
-- Set up the Serpent Serializer functions.
modpol.serpent = {}
dofile (modpol.topdir .. "/serpent/serpent.lua")
-- ===================================================================
-- This function stores "ledger" data to disk.
local store_ledger = function()
local ok = ocutil.file_write (modpol.file_ledger,
modpol.serpent.dump (modpol.ledger))
if ok ~= true then
ocutil.fatal_error ("store_data: ledger")
end
local nn = ocutil.table_length (modpol.ledger)
local str = "entries"
if nn == 1 then str = "entry" end
ocutil.log (nn .. " global ledger entries stored to disk")
end
-- ===================================================================
-- This function stores "orgs" data to disk.
local store_orgs = function()
local ok = ocutil.file_write (modpol.file_orgs,
modpol.serpent.dump (modpol.orgs))
if ok ~= true then
ocutil.fatal_error ("store_data: orgs")
end
local nn = ocutil.table_length (modpol.orgs)
local str = "entries"
if nn == 1 then str = "entry" end
ocutil.log (nn .. " orgs stored to disk")
end
-- ===================================================================
-- This function stores data to disk.
modpol.store_data = function()
store_ledger()
store_orgs()
end
-- ===================================================================
-- This function loads "ledger" data from disk.
local load_ledger = function()
local obj = ocutil.file_read (modpol.file_ledger )
if obj ~= nil then
local func, err = load (obj)
if err then
ocutil.fatal_error ("load_data: ledger" )
end
modpol.ledger = func()
local nn = ocutil.table_length (modpol.ledger)
local str = "entries"
if nn == 1 then str = "entry" end
ocutil.log (nn .. " global ledger entries loaded from disk")
else
ocutil.log ("No stored global ledger data found")
end
end
-- ===================================================================
-- This function loads "orgs" data from disk.
local load_orgs = function()
local obj = ocutil.file_read (modpol.file_orgs )
if obj ~= nil then
local func, err = load (obj)
if err then
ocutil.fatal_error ("load_data: orgs" )
end
modpol.orgs = func()
local nn = ocutil.table_length (modpol.orgs)
local str = "entries"
if nn == 1 then str = "entry" end
ocutil.log (nn .. " orgs loaded from disk")
else
ocutil.log ("No stored orgs data found")
end
end
-- ===================================================================
-- This function loads stored data from disk.
modpol.load_storage = function()
load_ledger()
load_orgs()
end
-- ===================================================================
-- End of file.

26
storage-mod_storage.lua Normal file
View File

@ -0,0 +1,26 @@
-- ===================================================================
-- /storage-mod_storage.lua
-- Persistent storage via Minetest's mod_storage method
-- See https://dev.minetest.net/StorageRef
-- Loads content of stored orgs and ledger from mod_storage
modpol.load_storage = function()
local mod_storage = minetest.get_mod_storage()
-- load orgs
local stored_orgs = minetest.deserialize(mod_storage:get_string("orgs"))
if (stored_orgs ~= nil) then
modpol.orgs = stored_orgs
end
-- load orgs
local stored_ledger = minetest.deserialize(mod_storage:get_string("ledger"))
if (stored_ledger ~= nil) then
modpol.ledger = stored_ledger
end
end
-- Stores content of current orgs and ledger to mod_storage
modpol.store_data = function()
-- write to storage
mod_storage:set_string("orgs", minetest.serialize(modpol.orgs))
mod_storage:set_string("ledger", minetest.serialize(modpol.ledger))
end

29
users.lua Normal file
View File

@ -0,0 +1,29 @@
-- ===================================================================
-- /users.lua
-- User-related functions for Modular Politics
-- Called by modpol.lua
-- ===================================================================
-- Function: modpol.list_users
-- Params: org
-- Outputs: Table of user names
--
-- This may be overwritten by the platform-specific interface
modpol.list_users = function(org)
local users = {}
if (org == nil) then -- no specified org; all players
if modpol.orgs["instance"]
and modpol.orgs["instance"]["members"] then
-- if instance exists and has membership
users = modpol.orgs["instance"]["members"]
else
users = {}
end
else -- if an org is specified
if (modpol.orgs[org] ~= nil) then -- org exists
users = modpol.orgs[org]["members"]
end
end
return users
end