Merge branch 'modules-as-actions' into 'master'
Merge modules-as-action refactor to main! See merge request medlabboulder/modpol!31
This commit is contained in:
commit
6aa09f9fdd
@ -6,6 +6,7 @@ One administrator, @ntnsndr, holds ultimate decision-making power over the proje
|
||||
* **Do-ocracy** Those who step forward to do a given task can decide how it should be done, in ongoing consultation with each other and the administrator.
|
||||
* **Membership** Participation is open to anyone who wants to contribute. The administrator can remove misbehaving participants at will for the sake of the common good.
|
||||
* **Code of Conduct** Participants are expected to abide by the Contributor Covenant (contributor-covenant.org).
|
||||
* **Ownership** This is a project of the Media Enterprise Design Lab at the University of Colorado Boulder and is owned by the university.
|
||||
|
||||
---
|
||||
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Media Enterprise Design Lab, University of Colorado Boulder
|
||||
Copyright (c) 2021 University of Colorado Boulder. A project of the Media Enterprise Design Lab (colorado.edu/lab/medlab/).
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
69
README.md
69
README.md
@ -1,54 +1,81 @@
|
||||
# Modular Politics Prototype for Minetest
|
||||
# Modular Politics for Minetest
|
||||
|
||||
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. In the future, it
|
||||
will be possible to use this framework to simulate governance in a
|
||||
number of platform contexts.
|
||||
Modular Politics is an extension that enables diverse governance processes on multi-user platforms. It offers a library that enables users to create or adapt their own modules that add specific governance functionalities.
|
||||
|
||||
This mod produces an API that can serve as a dependency for other mods that add specific governance functionalities.
|
||||
This implementation is a mod for [Minetest](https://minetest.net), a free/open-source voxel game. It is designed to be easily adapted to other multi-user platforms that also employ Lua as an extension language.
|
||||
|
||||
For background information and project roadmap, see [the wiki](https://gitlab.com/medlabboulder/modpol/-/wikis/home).
|
||||
## Design philosophy
|
||||
|
||||
Modular Politics seeks to implement a theoretical framework, also called "[modular politics](https://metagov.org/modpol)," which proposes these design goals:
|
||||
|
||||
* *Modularity*: Platform operators and community members should have the ability to construct systems by creating, importing, and arranging composable parts together as a coherent whole.
|
||||
* *Expressiveness*: The governance layer should be able to implement as wide a range of processes as possible.
|
||||
* *Portability*: Governance tools developed for one platform should be portable to another platform for reuse and adaptation.
|
||||
* *Interoperability*: Governance systems operating on different platforms and protocols should have the ability to interact with each other, sharing data and influencing each other's processes.
|
||||
|
||||
Additionally, Modular Politics seeks to counteract the tendency for "[implicit feudalism](https://ntnsndr.in/ImplicitFeudalism)," according to which rigid, top-down power structures are the norm in online spaces. To this end, some design patterns include:
|
||||
|
||||
* *Groups, not roles*: While most platforms assign powers through particular permissions given to individuals, in Modular Politics, power lies in groups (which Modular Politics calls "orgs").
|
||||
* *Consent, not oligarchy*: Rather than assuming that decisions will be made by a few power-holders, the software assumes that consent by all affected users is the norm.
|
||||
* *Inheritance, not blank slates*: When a new group is formed, it inherits the patterns of what preceded it, rather than imagining that it is starting from scratch.
|
||||
|
||||
It is certainly possible to use Modular Politics to replicate practices of implicit feudalism, such as all-powerful admins, but doing so requires extra work to overcome these defaults.
|
||||
|
||||
## Installation in Minetest
|
||||
|
||||
To use this in Minetest, simply install it in your mods/ or worldmods/ folder. Minetest will load init.lua.
|
||||
To use this in Minetest, simply install it in your `mods/` or `worldmods/` folder. Minetest will load `init.lua`.
|
||||
|
||||
In the game, open the Modular Politics interface with the command `/modpol`.
|
||||
In the game, open the Modular Politics dashboard with the command `/mp`.
|
||||
|
||||
For testing purposes, players with the `privs` privilege (generally admins) can use the `/mp` command, which resets all the orgs and opens a dashboard.
|
||||
|
||||
|
||||
## Standalone Version on the Command Line
|
||||
|
||||
Modular Politics can also be used independently of Minetest as a command-line tool. Currently command-line use of modpol requires a Unix-style system, but it is intended to become more fully platform independent.
|
||||
|
||||
The command-line version is in the `modpol` subdirectory. To interact with the interpreter on Unix systems in CLI mode, install lua or luajit and execute the following command in this directory:
|
||||
The command-line version is in the `modpol` subdirectory. To run the program on Unix systems in CLI mode, install lua or luajit and execute the following in this directory:
|
||||
|
||||
```
|
||||
$ cd modpol/
|
||||
$ lua [or luajit]
|
||||
> dofile("modpol.lua")
|
||||
$ lua[jit] login.lua
|
||||
```
|
||||
|
||||
For a list of global functions and tables, use `modpol.menu()`.
|
||||
You can also interact with the interpreter by starting it this way:
|
||||
|
||||
```
|
||||
$ lua[jit]
|
||||
> dofile("login.lua")
|
||||
```
|
||||
|
||||
In the interpreter, for a list of global functions and tables, use `modpol.menu()`.
|
||||
|
||||
## 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.
|
||||
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.
|
||||
Another storage method may be chosen in `modpol.lua`. A StorageRef-based method for Minetest 5.* is included: `storage-mod_storage.lua`.
|
||||
|
||||
|
||||
## Credits
|
||||
|
||||
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)."
|
||||
This project is led 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).
|
||||
|
||||
Other contributors include:
|
||||
Contributors include:
|
||||
|
||||
* [Luke Miller](https://gitlab.com/lukvmil) (main control flow, object orientation, module spec)
|
||||
* [MisterE](https://gitlab.com/gbrrudmin) (project refactoring, core feature development)
|
||||
* [Luke Miller](https://gitlab.com/lukvmil) (co-leadership, main control flow, object orientation, module spec)
|
||||
* [MisterE](https://gitlab.com/gbrrudmin) (early project refactoring, core feature development)
|
||||
* Robert Kiraly [[OldCoder](https://github.com/oldcoder/)] (ocutils.lua, storage-local.lua, project refactoring)
|
||||
* Skylar Hew (documentation)
|
||||
|
||||
We'd love to welcome 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).
|
||||
We are grateful for initial support for this project from a residency with [The Bentway Conservancy](https://www.thebentway.ca/). Read about us in _[The Field Guide to Digital and/as Public Space](https://www.thebentway.ca/stories/field-guide/)_.
|
||||
|
||||
|
||||
## Contributing
|
||||
|
||||
We'd love to welcome more contributors. Please join the conversation in the [Issues](https://gitlab.com/medlabboulder/modpol/-/issues), our [Matrix.org channel](https://matrix.to/#/#minetest-modpol:matrix.org), and the [Minetest.net forum](https://forum.minetest.net/viewtopic.php?f=47&t=26037).
|
||||
|
||||
Learn more about the project and how to develop your own modules in [the wiki](https://gitlab.com/medlabboulder/modpol/-/wikis/home).
|
||||
|
||||
We are grateful for support for this project from a residency with [The Bentway Conservancy](https://www.thebentway.ca/).
|
||||
|
||||
## Licenses
|
||||
|
||||
|
@ -1 +1 @@
|
||||
Provides an API for diverse governance mechanisms
|
||||
Provides a basis for diverse, user-configurable modules for in-game governance.
|
||||
|
4
init.lua
4
init.lua
@ -14,7 +14,7 @@ modpol = {}
|
||||
|
||||
-- get modpol_minetest's version of directory
|
||||
modpol.get_script_dir = function()
|
||||
return minetest.get_modpath("modpol").."/modpol"
|
||||
return minetest.get_modpath("modpol").."/modpol_core"
|
||||
end
|
||||
|
||||
-- TKTK: Implement minetest settingtypes for this... for now, the default is to use minetest mod storage.
|
||||
@ -34,7 +34,7 @@ modpol.storage_file_path = minetest.get_modpath("modpol").."/modpol_minetest/sto
|
||||
-- ===================================================================
|
||||
-- Load modpol system
|
||||
|
||||
dofile(minetest.get_modpath("modpol") .. "/modpol/modpol.lua")
|
||||
dofile(minetest.get_modpath("modpol") .. "/modpol_core/modpol.lua")
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
|
7
login.lua
Normal file
7
login.lua
Normal file
@ -0,0 +1,7 @@
|
||||
dofile("modpol_core/modpol.lua")
|
||||
|
||||
print("Log in as which user?")
|
||||
local username = io.read()
|
||||
|
||||
print()
|
||||
modpol.interactions.dashboard(username)
|
@ -1,35 +0,0 @@
|
||||
When calling modpol.lua from another file, one may wish to use
|
||||
different functions than those included by default.
|
||||
|
||||
So far, 2 definitions need to be made BEFORE calling modpol.lua, the rest can be overwritten
|
||||
after calling modpol.lua .
|
||||
|
||||
setup:
|
||||
----------------
|
||||
Define the global modpol table:
|
||||
modpol = {}
|
||||
|
||||
the following 2 items *may* be defined, if they are not, then defaults will be used:
|
||||
========================
|
||||
--------------------
|
||||
modpol.get_script_dir()
|
||||
--------------------
|
||||
type: function
|
||||
|
||||
defaults to: a function that can only work in linux
|
||||
|
||||
usage: must return the file path of the modpol directory. If modpol is in a
|
||||
larger directory, such as a mod filesystem, make sure that the path returned is the "base"
|
||||
modpol folder, i.e. the folder with all the original modpol files.
|
||||
========================
|
||||
--------------------
|
||||
modpol.storage_file_path
|
||||
--------------------
|
||||
type: string
|
||||
|
||||
defaults to: topdir .. "/storage/storage-local.lua",
|
||||
where topdir is the directory defined by modpol.storage_file_path
|
||||
|
||||
usage: if you wish to use another method of storage than that provided by default,
|
||||
then you can save a filepath to the storage script here.
|
||||
|
@ -1,50 +0,0 @@
|
||||
Ledger
|
||||
**************************
|
||||
|
||||
a legder is a table of records that holds the history of an org, and of modpol itself.
|
||||
|
||||
its structure is a table of legder entries.
|
||||
|
||||
Every org has one, starting with the entry recording its creation.
|
||||
|
||||
Ledger Entry
|
||||
**************************
|
||||
a legder entry is a table with the following properties:
|
||||
|
||||
timestamp = nil,
|
||||
entrytype = nil,
|
||||
action_msg = '',
|
||||
|
||||
the timestamp will hold the output of os.time at the moment the entry was created.
|
||||
|
||||
the entrytype is optional; it can hold an identifier of the kind of entry it is (e.g. 'org_init', 'new_user', etc etc.)
|
||||
|
||||
action_msg is the record output itself.
|
||||
|
||||
|
||||
|
||||
Entrytypes
|
||||
**************************
|
||||
|
||||
These are the entrytypes that modpol uses. Each additional module should document whatever entrytypes it adds.
|
||||
|
||||
'org_init'
|
||||
type used when an org is created
|
||||
|
||||
'org_purge'
|
||||
type used when all orgs are purged. -- will only be found in the main modpol ledger.
|
||||
|
||||
'org_remove'
|
||||
type used when removing an individual org
|
||||
|
||||
'add_member'
|
||||
type used when adding a member to an org
|
||||
|
||||
'remove_member'
|
||||
type used when removing a member from an org
|
||||
|
||||
|
||||
|
||||
Old orgs
|
||||
**************************
|
||||
when an org is removed, its ledger is stored in modpol.old_legders = {} Note that multiple ledgers may exist here with the same org name.
|
137
modpol/DOCS/orgs
137
modpol/DOCS/orgs
@ -1,137 +0,0 @@
|
||||
== Dev note ===
|
||||
*note, see doc ledgers for info about org ledgers.
|
||||
they are *not* just a message in a table anymore... they have a timestamp and more.
|
||||
===============
|
||||
|
||||
|
||||
Orgs are stored in modpol.orgs by the key 'org_id', a unique integer
|
||||
|
||||
Old ledgers of deleted orgs are stored in modpol.old_ledgers,
|
||||
if preserve records is enabled
|
||||
|
||||
An org is a table with the following properties:
|
||||
|
||||
{
|
||||
name = '', -- a unique name...
|
||||
this is a reference to the key used to store the org table. Changing this does not change the org name.
|
||||
policies = {}, -- a table to store the org's policies
|
||||
members = {}, -- a table that contains the member names
|
||||
or the org, in no particular order (TKTK: maybe change it so that members have a name:properties table?)
|
||||
ledger = {}, -- a table of ledger entries. See the ledger docs
|
||||
parent = nil, -- the name of the org that spawned this org.
|
||||
removing a parent org removes its children
|
||||
children = {}, -- a table of strings of org names of child orgs
|
||||
properties = { -- good for modules to store arbitrary info about the org here.
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
|
||||
API functions for orgs
|
||||
======================
|
||||
|
||||
a local variable setting 'preserve_records' determines whether
|
||||
to store old ledgers when an org is deleted. It is set to true.
|
||||
It would be possible to extend this to a settings file later.
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
Function: modpol.record(org_id, msg, type)
|
||||
-- Params: strings msg, type, org_id (number)
|
||||
-- Outputs:
|
||||
-- "msg" specifies an event and/or status message.
|
||||
-- "org" specifies an "org" name.
|
||||
-- "type" -- optional type of legder entry. Could be e.g. 'election_result', 'add_member', etc
|
||||
|
||||
|
||||
-- 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.
|
||||
-- the type input is used to be able to search ledgers for specific events
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
Function: modpol.add_org(org_id, members, parent, properties)
|
||||
-- Params: org)id (number), table members, string parent, table properties
|
||||
-- Parent must be an existing org, defaults to 'instance', properties
|
||||
-- are arbitrary properties to initialize the org with
|
||||
|
||||
-- Output:
|
||||
-- This function creates an "org". It returns a boolean success indicator and
|
||||
-- either an error message starting with "Error:" or a success message.
|
||||
--
|
||||
--
|
||||
-- The string parameter specifies the "org" name.
|
||||
--
|
||||
-- The members table should be an integer-indexed array of member
|
||||
-- names.
|
||||
|
||||
-- parent should be nil or a valid existing org name. If nil, defaults to
|
||||
-- 'instance'
|
||||
|
||||
-- properties defaults to an empty table. Use it to initialize the org
|
||||
-- with arbitrary properties
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
-- Function: modpol.remove_org(org_id, reason)
|
||||
-- Params: org_id (number), opt string reason
|
||||
-- Output:
|
||||
--
|
||||
-- This function removes an "org". It returns a boolean
|
||||
-- success indicator and either an error message
|
||||
-- starting with "Error:" or a success message. It also recursively
|
||||
-- removes all child orgs, with 'remove parent org' as reason. If
|
||||
-- preserve_records is enabled, it logs the removal in the org ledger,
|
||||
-- and stores the ledger in modpol.old_legders
|
||||
--
|
||||
-- The string parameter specifies the "org" name.
|
||||
--
|
||||
-- The reason is an optional string to additionally include in the
|
||||
-- log as reason for removal
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
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.
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
|
||||
Function: modpol.reset_orgs()
|
||||
-- Params: None
|
||||
-- Removes all orgs and recreates blank org "instance" with all
|
||||
-- current users
|
||||
-- returns confirmation message
|
||||
-- if preserve_records is enabled, stores all org ledgers
|
||||
-- in modpol.old_ledgers
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
Function: modpol.add_member(org_id, member)
|
||||
-- Params: org_id (number), member (string)
|
||||
-- Output:
|
||||
-- Adds the specified member to the specified org
|
||||
-- Returns a boolean success indicator and
|
||||
-- either a confirmation or error message
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
Function: modpol.is_member
|
||||
-- Params: org (number), member (string)
|
||||
-- Output: boolean, or nil and error message if not applicable. (org nonexistent)
|
||||
|
||||
|
||||
--==oo888888888888888888888888888888oo==--
|
||||
|
||||
Function: modpol.remove_member
|
||||
-- Params: org (number), member (string)
|
||||
-- Output:
|
||||
-- Removes the specified member from the specified org
|
||||
-- Returns confirmation or error message
|
@ -1,16 +0,0 @@
|
||||
--call all files in this directory
|
||||
|
||||
local localdir = modpol.topdir
|
||||
|
||||
--Users
|
||||
dofile (localdir .. "/users/users.lua")
|
||||
|
||||
--orgs
|
||||
dofile (localdir .. "/orgs/base.lua")
|
||||
dofile (localdir .. "/orgs/requests.lua")
|
||||
|
||||
--interactions
|
||||
dofile (localdir .. "/interactions/interactions.lua")
|
||||
|
||||
--modules
|
||||
dofile (localdir .. "/modules/consent.lua")
|
@ -1,7 +0,0 @@
|
||||
dofile("../modpol.lua")
|
||||
|
||||
print("Log in as which user?")
|
||||
local username = io.read()
|
||||
|
||||
print()
|
||||
modpol.interactions.dashboard(username)
|
@ -1,98 +0,0 @@
|
||||
modpol.modules = modpol.modules or {}
|
||||
|
||||
modpol.modules.consent = {}
|
||||
|
||||
-- sets consent to its own callback
|
||||
modpol.modules.consent.__index = modpol.modules.consent
|
||||
|
||||
function temp_consent_process()
|
||||
return {
|
||||
type = "consent",
|
||||
id = nil,
|
||||
org_id = nil,
|
||||
request_id = nil,
|
||||
total_votes = 0,
|
||||
majority_to_pass = 0.51,
|
||||
votes_needed = nil,
|
||||
votes_yes = {},
|
||||
votes_no = {}
|
||||
}
|
||||
end
|
||||
|
||||
-- ===============================================
|
||||
-- function to create a new consent process to resolve a pending process
|
||||
function modpol.modules.consent:new_process(id, request_id, org_id)
|
||||
local process = temp_consent_process()
|
||||
process.id = id
|
||||
process.request_id = request_id
|
||||
process.org_id = org_id
|
||||
|
||||
setmetatable(process, modpol.modules.consent)
|
||||
modpol.ocutil.log('Created new process #' .. id .. ' for request id #' .. request_id)
|
||||
|
||||
local p_org = modpol.orgs.get_org(org_id)
|
||||
|
||||
for i, member in ipairs(p_org.members) do
|
||||
p_org:add_pending_action(id, member)
|
||||
end
|
||||
|
||||
process.votes_needed = math.ceil(process.majority_to_pass * p_org:get_member_count())
|
||||
|
||||
return process
|
||||
end
|
||||
|
||||
-- ============================
|
||||
-- interact function for the consent module
|
||||
-- input: user (string)
|
||||
function modpol.modules.consent:interact(user)
|
||||
-- TODO this needs more context on the vote at hand
|
||||
modpol.interactions.binary_poll_user(
|
||||
user, "Do you consent?",
|
||||
function(vote)
|
||||
if vote == 'Yes' then
|
||||
self:approve(user, true)
|
||||
elseif vote == 'No' then
|
||||
self:approve(user, false)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- ======================================================
|
||||
-- function for users to vote on a pending request
|
||||
function modpol.modules.consent:approve(user, decision)
|
||||
if not modpol.orgs.get_org(self.org_id):has_member(user) then
|
||||
modpol.ocutil.log('Error in consent:approve -> user not a member of the org')
|
||||
return
|
||||
end
|
||||
|
||||
if decision then
|
||||
table.insert(self.votes_yes, user)
|
||||
modpol.ocutil.log('User ' .. user .. ' voted yes on request #' .. self.request_id)
|
||||
else
|
||||
table.insert(self.votes_no, user)
|
||||
modpol.ocutil.log('User ' .. user .. ' voted no on request #' .. self.request_id)
|
||||
end
|
||||
|
||||
self.total_votes = self.total_votes + 1
|
||||
|
||||
local p_org = modpol.orgs.get_org(self.org_id)
|
||||
p_org:remove_pending_action(self.id, user)
|
||||
|
||||
self:update_status()
|
||||
end
|
||||
|
||||
-- ===================================================
|
||||
-- determines whether process has finished and resolves request if it has (unfinished)
|
||||
function modpol.modules.consent:update_status()
|
||||
local process_org = modpol.orgs.get_org(self.org_id)
|
||||
|
||||
if #self.votes_yes >= self.votes_needed then
|
||||
modpol.ocutil.log('Request #' .. self.request_id .. ' passes')
|
||||
process_org:resolve_request(self.request_id, true)
|
||||
elseif #self.votes_no >= self.votes_needed then
|
||||
modpol.ocutil.log('Request #' .. self.request_id .. ' fails to pass')
|
||||
process_org:resolve_request(self.request_id, false)
|
||||
else
|
||||
modpol.ocutil.log('Waiting for more votes...')
|
||||
end
|
||||
end
|
@ -1,272 +0,0 @@
|
||||
modpol.orgs.request_params = {
|
||||
add_org = 1,
|
||||
delete = 0,
|
||||
add_member = 1,
|
||||
remove_member = 1
|
||||
}
|
||||
|
||||
-- ================================
|
||||
-- creates a new process linked to a request id
|
||||
function modpol.orgs:create_process(process_type, request_id)
|
||||
if not modpol.modules[process_type] then
|
||||
modpol.ocutil.log('Process type "' .. process_type .. '" does not exist')
|
||||
return
|
||||
end
|
||||
|
||||
local empty_index = nil
|
||||
-- linear search for empty process slots (lazy deletion)
|
||||
for k, v in ipairs(self.processes) do
|
||||
if v == 'deleted' then
|
||||
empty_index = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local index
|
||||
-- attempts to fill empty spots in list, otherwise appends to end
|
||||
if empty_index then
|
||||
index = empty_index
|
||||
else
|
||||
index = #self.processes + 1
|
||||
end
|
||||
|
||||
-- retrieving requested module
|
||||
local module = modpol.modules[process_type]
|
||||
local new_process = module:new_process(index, request_id, self.id)
|
||||
|
||||
self.processes[index] = new_process
|
||||
|
||||
modpol.ocutil.log('Created process #' .. index .. ' - ' .. process_type .. ' for ' .. self.name)
|
||||
self:record('Created process #' .. index .. ' - ' .. process_type, 'add_process')
|
||||
|
||||
return index
|
||||
end
|
||||
|
||||
-- ===========================
|
||||
-- adds a new pending action to the org's table
|
||||
function modpol.orgs:add_pending_action(process_id, user)
|
||||
-- adds tables if they don't exist already
|
||||
self.pending[user] = self.pending[user] or {}
|
||||
-- flagging actual action
|
||||
self.pending[user][process_id] = true
|
||||
|
||||
local message =
|
||||
modpol.interactions.message(user, "New pending action in " .. self.name)
|
||||
modpol.ocutil.log("Added pending action to " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
|
||||
self:record('Added pending action to ' .. user .. ' in process #' .. process_id, "add_pending_action")
|
||||
end
|
||||
|
||||
-- ========================
|
||||
-- removes a pending action from the org's table
|
||||
function modpol.orgs:remove_pending_action(process_id, user)
|
||||
|
||||
if self.pending[user] then
|
||||
self.pending[user][process_id] = nil
|
||||
modpol.ocutil.log("Removed pending action from " .. user .. " in process #" .. process_id .. ' from ' .. self.name)
|
||||
self:record('Removed pending action from ' .. user .. ' in process #' .. process_id, "del_pending_action")
|
||||
else
|
||||
modpol.ocutil.log("Could not remove pending action from " .. user .. " in process #" .. process_id)
|
||||
end
|
||||
end
|
||||
|
||||
-- =====================
|
||||
-- removes all pending actions for a given process id from all users
|
||||
function modpol.orgs:wipe_pending_actions(process_id)
|
||||
for user in pairs(self.pending) do
|
||||
self.pending[user][process_id] = nil
|
||||
end
|
||||
modpol.ocutil.log("Removed all pending actions for process #" .. process_id)
|
||||
self:record('Removed all pending actions for process #' .. process_id, "del_pending_action")
|
||||
end
|
||||
|
||||
-- ======================
|
||||
-- returns a boolean to indicate whether a user has any active pending actions
|
||||
function modpol.orgs:has_pending_actions(user)
|
||||
-- next() will return the next pair in a table
|
||||
-- if next() returns nil, the table is empty
|
||||
if not self.pending[user] then
|
||||
return false
|
||||
else
|
||||
if not next(self.pending[user]) then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- ===========================
|
||||
-- compares to requests to see if they are identical
|
||||
function modpol.orgs.comp_req(req1, req2)
|
||||
-- compares request type
|
||||
if req1.type ~= req2.type then
|
||||
return false
|
||||
else
|
||||
-- comparing parameters
|
||||
-- we can assume the number of params is the same as this is checked in the make_request func
|
||||
for k, v in ipairs(req1.params) do
|
||||
if v ~= req2.params[k] then
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
-- ===============================
|
||||
-- returns string of all active requests
|
||||
function modpol.orgs:list_request()
|
||||
local str
|
||||
for id, req in ipairs(self.requests) do
|
||||
if req ~= "deleted" then
|
||||
if str then
|
||||
str = str .. '\n' .. req.type .. ' (' .. req.user .. ') '
|
||||
else
|
||||
str = req.type .. ' (' .. req.user .. ') '
|
||||
end
|
||||
end
|
||||
end
|
||||
return str
|
||||
end
|
||||
|
||||
-- ===============================
|
||||
-- if the request was approved, the associated function is called, otherwise it is deleted
|
||||
-- TODO Rather than hard-coding functions below, this should be given an arbitrary result function based on the request
|
||||
function modpol.orgs:resolve_request(request_id, approve)
|
||||
|
||||
-- wipe actions
|
||||
self:wipe_pending_actions(request_id)
|
||||
|
||||
if approve then
|
||||
local request = self.requests[request_id]
|
||||
local p = request.params
|
||||
|
||||
-- there's probably a way to clean this up, the issue is the varying number of commands
|
||||
-- ex: self['add_member'](self, 'member_name')
|
||||
-- not sure if this is safe, more testing to do
|
||||
|
||||
-- self[request.type](self, p[1], p[2], p[3])
|
||||
|
||||
if request.type == "add_org" then
|
||||
self:add_org(request.params[1], request.user)
|
||||
elseif request.type == "delete" then
|
||||
self:delete()
|
||||
elseif request.type == "add_member" then
|
||||
self:add_member(request.params[1])
|
||||
elseif request.type == "remove_member" then
|
||||
self:remove_member(request.params[1])
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
self.processes[request_id] = "deleted"
|
||||
modpol.ocutil.log('Deleted process #' .. request_id)
|
||||
|
||||
self.requests[request_id] = "deleted"
|
||||
modpol.ocutil.log("Resolved request #" .. request_id .. ' in ' .. self.name)
|
||||
|
||||
self:record("Resolved request #" .. request_id, "resolve_request")
|
||||
end
|
||||
|
||||
-- ================================
|
||||
-- tries to make a request to the org
|
||||
function modpol.orgs:make_request(request)
|
||||
-- makes sure the request has the valid number of parameters
|
||||
local num_params = modpol.orgs.request_params[request.type]
|
||||
|
||||
if num_params == nil then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request type is invalid')
|
||||
return false
|
||||
end
|
||||
|
||||
-- request.params should match the num of params for that type
|
||||
if #request.params ~= num_params then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has invalid number of parameters')
|
||||
return false
|
||||
end
|
||||
|
||||
-- checking to see if identical request already exists
|
||||
for k, v in ipairs(self.requests) do
|
||||
if self.comp_req(request, v) == true then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> request has already been made')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- checking to see if user is able to make request
|
||||
local requested_policy = self.policies[request.type]
|
||||
|
||||
-- if not the instance org (instance's don't have parents)
|
||||
if self.id ~= 1 then
|
||||
local parent_policy = modpol.orgs.get_org(self.parent).policies[request.type]
|
||||
|
||||
-- tries to use org's policy table, defers to parent otherwise
|
||||
if not requested_policy then
|
||||
modpol.ocutil.log(request.type .. ' policy not found, deferring to parent org')
|
||||
requested_policy = parent_policy
|
||||
|
||||
if not parent_policy then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> parent policy undefined')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- fails if instance policy undefined
|
||||
else
|
||||
if not requested_policy then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> policy undefined')
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
-- make sure user is allowed to make request
|
||||
if requested_policy.must_be_member and not self:has_member(request.user) then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':make_request -> user must be org member to make this request')
|
||||
return false
|
||||
end
|
||||
|
||||
local empty_index = nil
|
||||
-- linear search for empty process slots (lazy deletion)
|
||||
for k, v in ipairs(self.requests) do
|
||||
if v == 'deleted' then
|
||||
empty_index = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
-- attempts to fill empty spots in list, otherwise appends to end
|
||||
local request_id = nil
|
||||
if empty_index then
|
||||
self.requests[empty_index] = request
|
||||
request_id = empty_index
|
||||
else
|
||||
table.insert(self.requests, request)
|
||||
request_id = #self.requests
|
||||
end
|
||||
|
||||
modpol.interactions.message(request.user, "Request made by " .. request.user .. " to " .. request.type .. " in " .. self.name)
|
||||
modpol.ocutil.log("Request made by " .. request.user .. " to " .. request.type .. " in " .. self.name)
|
||||
self:record("Request made by " .. request.user .. " to " .. request.type, "make_request")
|
||||
|
||||
-- launching process tied to this request
|
||||
local process_id = self:create_process(requested_policy.process_type, request_id)
|
||||
|
||||
-- returns process id of processes launched by this request
|
||||
return process_id
|
||||
end
|
||||
|
||||
-- wrapper for process:interact function, ensures that user actually has a pending action for that process
|
||||
function modpol.orgs:interact(process_id, user)
|
||||
process = self.processes[process_id]
|
||||
if self.pending[user] then
|
||||
if self.pending[user][process_id] == true then
|
||||
process:interact(user)
|
||||
else
|
||||
modpol.ocutil.log("Cannot interact with process, user does not have a valid pending action")
|
||||
end
|
||||
else
|
||||
modpol.ocutil.log("Cannot interact with process, user does not have any pending actions")
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,31 +0,0 @@
|
||||
dofile("../modpol.lua")
|
||||
|
||||
print('\nRemoving existing orgs')
|
||||
modpol.orgs.reset()
|
||||
|
||||
print('\nCreating an org called "test_org"')
|
||||
test_org = modpol.instance:add_org('test_org', 'luke')
|
||||
|
||||
print('\nAdding user "nathan" to test_org')
|
||||
test_org:add_member('nathan')
|
||||
|
||||
print("\nOkay, let's start with requests. Setting up an org...")
|
||||
modpol.instance:add_org('test_org', 'luke')
|
||||
test_org:add_member('nathan')
|
||||
|
||||
print('\nMaking consent the add_member policy')
|
||||
test_org:set_policy("add_member", "consent", false);
|
||||
|
||||
print('\nMaking a request to add Josh')
|
||||
add_josh = {
|
||||
user = "josh",
|
||||
type = "add_member",
|
||||
params = {"josh"}
|
||||
}
|
||||
request_id = test_org:make_request(add_josh)
|
||||
|
||||
|
||||
print('\nHave the two members vote on it')
|
||||
modpol.interactions.org_dashboard("nathan","test_org")
|
||||
modpol.interactions.org_dashboard("luke","test_org")
|
||||
|
@ -1,29 +0,0 @@
|
||||
-- ===================================================================
|
||||
-- /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.instance
|
||||
and modpol.instance.members then
|
||||
-- if instance exists and has membership
|
||||
users = modpol.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
|
23
modpol_core/api.lua
Normal file
23
modpol_core/api.lua
Normal file
@ -0,0 +1,23 @@
|
||||
--call all files in this directory
|
||||
|
||||
local localdir = modpol.topdir
|
||||
|
||||
--orgs
|
||||
dofile (localdir .. "/orgs/base.lua")
|
||||
dofile (localdir .. "/orgs/process.lua")
|
||||
|
||||
--interactions
|
||||
dofile (localdir .. "/interactions/interactions.lua")
|
||||
|
||||
--modules
|
||||
--TODO make this automatic and directory-based
|
||||
dofile (localdir .. "/modules/add_child_org_consent.lua")
|
||||
dofile (localdir .. "/modules/change_modules.lua")
|
||||
dofile (localdir .. "/modules/consent.lua")
|
||||
dofile (localdir .. "/modules/join_org_consent.lua")
|
||||
dofile (localdir .. "/modules/leave_org.lua")
|
||||
dofile (localdir .. "/modules/remove_child_consent.lua")
|
||||
dofile (localdir .. "/modules/remove_member_consent.lua")
|
||||
dofile (localdir .. "/modules/remove_org_consent.lua")
|
||||
dofile (localdir .. "/modules/remove_org.lua")
|
||||
dofile (localdir .. "/modules/rename_org_consent.lua")
|
@ -20,7 +20,7 @@ function modpol.interactions.dashboard(user)
|
||||
modpol.instance:add_member(user)
|
||||
end
|
||||
|
||||
local all_users = modpol.list_users()
|
||||
local all_users = modpol.instance:list_members()
|
||||
|
||||
print('All orgs: (user orgs indicated by *)')
|
||||
for id, org in ipairs(modpol.orgs.array) do
|
||||
@ -34,25 +34,27 @@ function modpol.interactions.dashboard(user)
|
||||
print('All users: ' .. table.concat(all_users, ', '))
|
||||
print()
|
||||
|
||||
print('Access which org?')
|
||||
|
||||
print('Access which org id?')
|
||||
local sel = io.read()
|
||||
print()
|
||||
local sel_org = modpol.orgs.array[tonumber(sel)].name
|
||||
|
||||
if not sel_org then return end
|
||||
|
||||
modpol.interactions.org_dashboard(user, sel_org)
|
||||
if modpol.orgs.array[tonumber(sel)] then
|
||||
local sel_org = modpol.orgs.array[tonumber(sel)].name
|
||||
modpol.interactions.org_dashboard(user, sel_org)
|
||||
else
|
||||
print("Org id not found.")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Function: modpol.interactions.org_dashboard
|
||||
-- Params: user (string), org_name (string)
|
||||
-- Params: user (string), org_string (string or id)
|
||||
-- Output: Displays a menu of org-specific commands to the user
|
||||
function modpol.interactions.org_dashboard(user, org_name)
|
||||
local org = modpol.orgs.get_org(org_name)
|
||||
function modpol.interactions.org_dashboard(user, org_string)
|
||||
local org = modpol.orgs.get_org(org_string)
|
||||
if not org then return nil end
|
||||
|
||||
-- identify parent
|
||||
local parent = ""
|
||||
if org.id == 1 then
|
||||
parent = "none"
|
||||
@ -60,66 +62,67 @@ function modpol.interactions.org_dashboard(user, org_name)
|
||||
parent = modpol.orgs.get_org(org.parent).name
|
||||
end
|
||||
|
||||
-- identify children
|
||||
local children = {}
|
||||
for k,v in ipairs(org.children) do
|
||||
local this_child = modpol.orgs.get_org(v)
|
||||
table.insert(children, this_child.name)
|
||||
end
|
||||
|
||||
local process_msg = #org.processes .. " total"
|
||||
-- list available modules
|
||||
local org_modules = {}
|
||||
for k,v in pairs(org.modules) do
|
||||
if not v.hide then
|
||||
table.insert(org_modules, v.slug)
|
||||
end
|
||||
end
|
||||
|
||||
-- list pending
|
||||
local process_msg = #org.processes .. " total processes"
|
||||
if org.pending[user] then
|
||||
process_msg = process_msg .. " (" .. #org.pending[user] .. " pending)"
|
||||
else
|
||||
process_msg = process_msg .. " (0 pending)"
|
||||
end
|
||||
|
||||
|
||||
-- set up output
|
||||
print("Org: " .. org_name)
|
||||
print("Org: " .. org.name)
|
||||
print("Parent: " .. parent)
|
||||
print("Members: " .. table.concat(org.members, ", "))
|
||||
print("Children: " .. table.concat(children, ", "))
|
||||
print("Processes: " .. process_msg)
|
||||
print("Modules: " .. table.concat(org_modules, ", "))
|
||||
print("Pending: " .. process_msg)
|
||||
print()
|
||||
print("Commands: (L)eave, (J)oin, (P)rocesses, (A)dd child, (D)elete org")
|
||||
print("Commands: (M)odules, (P)ending")
|
||||
|
||||
local sel = io.read()
|
||||
print()
|
||||
|
||||
if sel == 'l' or sel == 'L' then
|
||||
org:remove_member(user)
|
||||
|
||||
elseif sel == 'j' or sel == 'J' then
|
||||
org:make_request({user=user, type="add_member", params={user}})
|
||||
|
||||
if sel == 'm' or sel == 'M' then
|
||||
print("Type module name: ")
|
||||
local module_sel = io.read()
|
||||
print()
|
||||
local module_result = false
|
||||
for k,v in ipairs(org_modules) do
|
||||
if v == module_sel then
|
||||
module_result = true
|
||||
end
|
||||
end
|
||||
if module_result then
|
||||
org:call_module(module_sel, user)
|
||||
else
|
||||
print("Error: Module not found.")
|
||||
end
|
||||
|
||||
elseif sel == 'a' or sel == 'A' then
|
||||
print("What should the new org be named?")
|
||||
local new_org_name = io.read()
|
||||
org:make_request({user=user, type="add_org", params={new_org_name}})
|
||||
|
||||
elseif sel == 'd' or sel == 'D' then
|
||||
org:make_request({user=user, type="delete", params={}})
|
||||
|
||||
elseif sel == 'p' or sel == 'P' then
|
||||
local processes = {}
|
||||
print("All processes: (* indicates pending action)")
|
||||
print("All processes: (* indicates pending)")
|
||||
for k,v in ipairs(org.processes) do
|
||||
local this_request = org.requests[v.request_id]
|
||||
if type(this_request) == "table" then
|
||||
local active = ''
|
||||
if org.pending[user] then
|
||||
if org.pending[user][v.id] then
|
||||
active = '*'
|
||||
end
|
||||
local active = ''
|
||||
if org.pending[user] then
|
||||
if org.pending[user][v.id] then
|
||||
active = '*'
|
||||
end
|
||||
local req_str = "[" .. v.id .. "] " ..
|
||||
active .. this_request.type
|
||||
if this_request.params[1] then
|
||||
req_str = req_str .. ": " ..
|
||||
table.concat(this_request.params, ", ")
|
||||
end
|
||||
print(req_str)
|
||||
end
|
||||
end
|
||||
print()
|
||||
@ -129,14 +132,13 @@ function modpol.interactions.org_dashboard(user, org_name)
|
||||
if not process then return end
|
||||
if org:has_pending_actions(user) then
|
||||
if org.pending[user][process.id] then
|
||||
process:interact(user)
|
||||
org:interact(process.id, user)
|
||||
end
|
||||
end
|
||||
else
|
||||
print("Command not found")
|
||||
modpol.interactions.org_dashboard(user, org.name)
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
end
|
||||
|
||||
-- Function: modpol.interactions.policy_dashboard
|
||||
@ -228,8 +230,15 @@ end
|
||||
-- ====================
|
||||
|
||||
-- Function: modpol.interactions.message_org
|
||||
-- input: initiator (string), org_id (number), message (string)
|
||||
-- input: initiator (string), org (number or string), message (string)
|
||||
-- output: broadcasts message to all org members
|
||||
function modpol.interactions.message_org(initiator, org, message)
|
||||
local this_org = modpol.orgs.get_org(org)
|
||||
local users = this_org:list_members()
|
||||
for k,v in ipairs(users) do
|
||||
modpol.interactions.message(v, message)
|
||||
end
|
||||
end
|
||||
|
||||
-- Function: modpol.interactions.binary_poll_org
|
||||
-- input: initator (user string), org_id (number)
|
@ -44,6 +44,9 @@ print (topdir)
|
||||
-- OldCoder utilities
|
||||
dofile (topdir .. "/util/ocutil/ocutil.lua")
|
||||
|
||||
-- Misc. utilities
|
||||
dofile (topdir .. "/util/misc.lua")
|
||||
|
||||
-- ===================================================================
|
||||
-- Persistent storage
|
||||
-- must implement modpol.load_storage() and modpol.store_data()
|
||||
@ -63,12 +66,19 @@ dofile (modpol.storage_file_path)
|
||||
-- If available, load persistent storage into active tables
|
||||
modpol.load_storage()
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- ModPol core features
|
||||
-- Modpol modules
|
||||
|
||||
modpol.modules = modpol.modules or {}
|
||||
|
||||
-- TKTK need to specify modules to include
|
||||
|
||||
-- ===================================================================
|
||||
-- Modpol core features
|
||||
|
||||
dofile (topdir .. "/api.lua")
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Final checks
|
||||
|
||||
@ -90,7 +100,6 @@ end
|
||||
-- create instance if not present
|
||||
modpol.instance = modpol.orgs.array[1] or modpol.orgs.init_instance()
|
||||
|
||||
|
||||
modpol.ocutil.log ("modpol loaded")
|
||||
|
||||
-- ===================================================================
|
75
modpol_core/modules/add_child_org_consent.lua
Normal file
75
modpol_core/modules/add_child_org_consent.lua
Normal file
@ -0,0 +1,75 @@
|
||||
--- @module add_child_org_consent
|
||||
-- Adds a child org
|
||||
-- Depends on `consent`
|
||||
|
||||
local add_child_org_consent = {
|
||||
name = "Add child org (consent)",
|
||||
slug = "add_child_org_consent",
|
||||
desc = "Create a child org in the current one with member consent"
|
||||
}
|
||||
add_child_org_consent.data = {
|
||||
child_name = "",
|
||||
result = nil
|
||||
}
|
||||
add_child_org_consent.config = {
|
||||
}
|
||||
|
||||
-- @function initiate
|
||||
function add_child_org_consent:initiate(result)
|
||||
modpol.interactions.text_query(
|
||||
self.initiator,"Child org name: ",
|
||||
function(input)
|
||||
if input == "" then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"No name entered for child org")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
self.org:delete_process(self.id)
|
||||
if result then result() end
|
||||
return
|
||||
elseif modpol.orgs.get_org(input) then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Org name already in use")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
self.org:delete_process(self.id)
|
||||
if result then result() end
|
||||
return
|
||||
end
|
||||
self.data.child_name = input
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Proposed child org: " .. input)
|
||||
-- initiate consent process
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Create child org " ..
|
||||
self.data.child_name .. "?",
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function()
|
||||
self:create_child_org()
|
||||
end
|
||||
)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function add_child_org_consent:create_child_org()
|
||||
self.org:add_org(self.data.child_name, self.initiator)
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,
|
||||
self.org.name,
|
||||
"Child org created: "..self.data.child_name)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.add_child_org_consent = add_child_org_consent
|
171
modpol_core/modules/change_modules.lua
Normal file
171
modpol_core/modules/change_modules.lua
Normal file
@ -0,0 +1,171 @@
|
||||
--- change_modules
|
||||
-- @module change_modules
|
||||
-- Depends on consent
|
||||
|
||||
local change_modules = {
|
||||
name = "Change modules (consent)",
|
||||
slug = "change_modules",
|
||||
desc = "Add or remove modules from the org with member consent",
|
||||
hide = false;
|
||||
}
|
||||
|
||||
change_modules.data = {
|
||||
result = nil
|
||||
}
|
||||
|
||||
change_modules.config = {
|
||||
}
|
||||
|
||||
function change_modules:initiate(result)
|
||||
self.data.result = result
|
||||
-- Step 1: add or remove?
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator, "Module change options:",
|
||||
{"Add module","Remove module"},
|
||||
function(input)
|
||||
if input == "Add module" then
|
||||
self:add_module()
|
||||
elseif input == "Remove module" then
|
||||
self:remove_module()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function change_modules:add_module()
|
||||
-- prepare module options
|
||||
local available_modules = modpol.copy_table(modpol.modules)
|
||||
for k,org_mod in pairs(self.org.modules) do
|
||||
if available_modules[org_mod.slug] then
|
||||
available_modules[org_mod.slug] = nil
|
||||
end end
|
||||
-- present module options
|
||||
local modules_list = {}
|
||||
for k,v in pairs(available_modules) do
|
||||
table.insert(modules_list,v.name)
|
||||
end
|
||||
if #modules_list == 0 then
|
||||
modpol.interactions.message(
|
||||
self.initiator, "Org has all modules")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.id)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
return
|
||||
end
|
||||
table.sort(modules_list)
|
||||
-- now ask which to add
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator, "Choose a module to add:",
|
||||
modules_list,
|
||||
function(mod_choice)
|
||||
-- confirm choice
|
||||
modpol.interactions.binary_poll_user(
|
||||
self.initiator,
|
||||
"Confirm: propose to add module \"" ..
|
||||
mod_choice .. "\"?",
|
||||
function(input)
|
||||
if input == "Yes" then
|
||||
self:propose_change("add",mod_choice)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.id)
|
||||
else
|
||||
self:add_module()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function change_modules:remove_module()
|
||||
-- prepare module options
|
||||
local available_modules = {}
|
||||
for k,org_mod in pairs(self.org.modules) do
|
||||
if not org_mod.hide then
|
||||
available_modules[org_mod.slug] = modpol.copy_table(org_mod)
|
||||
end end
|
||||
local modules_list = {}
|
||||
local modules_count = 0
|
||||
for k,v in pairs(available_modules) do
|
||||
table.insert(modules_list,v.name)
|
||||
modules_count = modules_count + 1
|
||||
end
|
||||
-- abort if no modules to remove
|
||||
if modules_count == 0 then
|
||||
modpol.interactions.message(
|
||||
self.initiator, "Org has no modules")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.id)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
return
|
||||
end
|
||||
table.sort(modules_list)
|
||||
-- now ask which to remove
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator, "Choose a module to remove:",
|
||||
modules_list,
|
||||
function(mod_choice)
|
||||
-- confirm choice
|
||||
modpol.interactions.binary_poll_user(
|
||||
self.initiator,
|
||||
"Confirm: propose to remove module \"" .. mod_choice .. "\"?",
|
||||
function(input)
|
||||
if input == "Yes" then
|
||||
self:propose_change("remove",mod_choice)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.id)
|
||||
else
|
||||
self:remove_module()
|
||||
end
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
--- propose_change
|
||||
-- @field type "add" or "remove"
|
||||
function change_modules:propose_change(type, mod_text)
|
||||
self.org:call_module(
|
||||
"consent",self.initiator,
|
||||
{
|
||||
prompt = "Do you consent to "..type..
|
||||
" this module in org "..self.org.name..
|
||||
":\n"..mod_text,
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function()
|
||||
if type == "add" then
|
||||
for k,v in pairs(modpol.modules) do
|
||||
if v.name == mod_text then
|
||||
table.insert(self.org.modules,v)
|
||||
end
|
||||
end
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,self.org.id,
|
||||
"Consent reached:\nAdding \""
|
||||
..mod_text.."\" to org "..self.org.name)
|
||||
elseif type == "remove" then
|
||||
modpol.msg("Removing!")
|
||||
local i = 0
|
||||
for k,v in pairs(self.org.modules) do
|
||||
i = i + 1
|
||||
if v.name == mod_text then
|
||||
modpol.msg("got it!")
|
||||
self.org.modules[k] = nil
|
||||
end
|
||||
end
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,self.org.id,
|
||||
"Consent reached:\nRemoving \""
|
||||
..mod_text.."\" from org "..self.org.name)
|
||||
end
|
||||
end)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.change_modules = change_modules
|
56
modpol_core/modules/consent.lua
Normal file
56
modpol_core/modules/consent.lua
Normal file
@ -0,0 +1,56 @@
|
||||
--- @module consent
|
||||
-- A utility module for checking consent
|
||||
|
||||
local consent = {
|
||||
name = "Consent process utility",
|
||||
slug = "consent",
|
||||
desc = "A module other modules use for consent decisions",
|
||||
hide = true
|
||||
}
|
||||
|
||||
consent.data = {
|
||||
votes = 0
|
||||
}
|
||||
|
||||
consent.config = {
|
||||
prompt = "Do you consent?",
|
||||
votes_required = 1
|
||||
}
|
||||
|
||||
function consent:initiate(result)
|
||||
self.data.result = result
|
||||
-- if org is empty, consent is given automatically
|
||||
if self.org:get_member_count() == 0 then
|
||||
if self.data.result then
|
||||
self.data.result() end
|
||||
self.org:wipe_pending_actions(self.id)
|
||||
else
|
||||
-- otherwise, create poll
|
||||
for id, member in pairs(self.org.members) do
|
||||
self.org:add_pending_action(self.id, member, "callback")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function consent:callback(member)
|
||||
modpol.interactions.binary_poll_user(
|
||||
member,
|
||||
self.config.prompt,
|
||||
function (resp)
|
||||
self.org:remove_pending_action(self.id,member)
|
||||
if resp == "Yes" then
|
||||
self.data.votes = self.data.votes + 1
|
||||
end
|
||||
if self.data.votes >= self.config.votes_required then
|
||||
if self.data.result then
|
||||
self.data.result() end
|
||||
self.org:wipe_pending_actions(self.id)
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
modpol.interactions.org_dashboard(
|
||||
member, self.org.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
modpol.modules.consent = consent
|
39
modpol_core/modules/join_org.lua
Normal file
39
modpol_core/modules/join_org.lua
Normal file
@ -0,0 +1,39 @@
|
||||
|
||||
join_org = {}
|
||||
|
||||
join_org.setup = {
|
||||
name = "Join Org",
|
||||
slug = "join_org",
|
||||
desc = "If consent process is passed, initiator joins this org."
|
||||
}
|
||||
|
||||
function join_org.initiate(result)
|
||||
modpol.interactions.binary_poll_user(
|
||||
initiator,
|
||||
"Would you like to join " .. org.name,
|
||||
function (resp)
|
||||
if resp == "Yes" then
|
||||
self.org:add_member(self.initiator)
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
for i, member in ipairs(self.org.members) do
|
||||
self.org:add_pending_action(
|
||||
member,
|
||||
function ()
|
||||
modpol.interactions.binary_poll_user(
|
||||
member,
|
||||
"Let " .. initiator .. " join " .. org.name .. "?",
|
||||
function (resp)
|
||||
|
||||
end
|
||||
)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
if result then result() end
|
||||
end
|
||||
|
||||
modpol.modules.join_org = join_org
|
51
modpol_core/modules/join_org_consent.lua
Normal file
51
modpol_core/modules/join_org_consent.lua
Normal file
@ -0,0 +1,51 @@
|
||||
--- Join org (consent)
|
||||
-- A simple module that calls a consent process on an org to add a member.
|
||||
-- Depends on the Consent module.
|
||||
|
||||
local join_org_consent = {
|
||||
name = "Join this org (consent)",
|
||||
slug = "join_org_consent",
|
||||
desc = "Adds member with consent of all members"
|
||||
}
|
||||
|
||||
join_org_consent.data = {
|
||||
result = nil
|
||||
}
|
||||
|
||||
join_org_consent.config = {
|
||||
}
|
||||
|
||||
function join_org_consent:initiate(result)
|
||||
if self.org:has_member(self.initiator) then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"You are already a member of this org")
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
else
|
||||
self.data.result = result
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Allow " .. self.initiator .. " to join?",
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function ()
|
||||
self:complete()
|
||||
end
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
function join_org_consent:complete()
|
||||
self.org:add_member(self.initiator)
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,self.org.name,
|
||||
"Consent reached: " .. self.initiator ..
|
||||
" joined org " .. self.org.name)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
modpol.modules.join_org_consent = join_org_consent
|
39
modpol_core/modules/leave_org.lua
Normal file
39
modpol_core/modules/leave_org.lua
Normal file
@ -0,0 +1,39 @@
|
||||
--- leave_org
|
||||
-- @module leave_org
|
||||
|
||||
local leave_org = {
|
||||
name = "Leave org",
|
||||
slug = "leave_org",
|
||||
desc = "Remove yourself from the current org"
|
||||
}
|
||||
|
||||
leave_org.data = {
|
||||
}
|
||||
|
||||
leave_org.config = {
|
||||
}
|
||||
|
||||
--- (Required): initiate function
|
||||
-- Modules have access to the following instance variables:
|
||||
-- @param result (optional) Callback if this module is embedded in other modules
|
||||
-- @function initiate
|
||||
function leave_org:initiate(result)
|
||||
if self.org == modpol.instance then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"You cannot leave the root org")
|
||||
else
|
||||
self.org:remove_member(self.initiator)
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,self.org.id,
|
||||
self.initiator .. " has left org " .. self.org.name)
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"You have left org " .. self.org.name)
|
||||
end
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.leave_org = leave_org
|
71
modpol_core/modules/remove_child_consent.lua
Normal file
71
modpol_core/modules/remove_child_consent.lua
Normal file
@ -0,0 +1,71 @@
|
||||
--- Remove child (consent)
|
||||
-- A simple module that calls a consent process on an org to remove its child
|
||||
-- Depends on the Consent module.
|
||||
|
||||
local remove_child_consent = {
|
||||
name = "Remove child (consent)",
|
||||
slug = "remove_child_consent",
|
||||
desc = "Removes a child org if all members of this org consent."
|
||||
}
|
||||
|
||||
remove_child_consent.data = {
|
||||
result = nil,
|
||||
child_to_remove = nil
|
||||
}
|
||||
|
||||
remove_child_consent.config = {
|
||||
}
|
||||
|
||||
function remove_child_consent:initiate(result)
|
||||
local children = {}
|
||||
for i,v in ipairs(self.org.children) do
|
||||
local child = modpol.orgs.get_org(v)
|
||||
if child then table.insert(children, child.name) end
|
||||
end
|
||||
-- Abort if no child orgs
|
||||
if #children == 0 then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Org has no children")
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
else
|
||||
self.data.result = result
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator,
|
||||
"Choose a child of org "..
|
||||
self.org.name.." to remove:",
|
||||
children,
|
||||
function(input)
|
||||
self.data.child_to_remove = modpol.orgs.get_org(input)
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Remove child org "..input.."?",
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function()
|
||||
self:complete()
|
||||
end)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function remove_child_consent:complete()
|
||||
modpol.interactions.message_org(
|
||||
self.initiator, self.data.child_to_remove.id,
|
||||
"Removing org " .. self.data.child_to_remove.name ..
|
||||
" by parent org consent")
|
||||
modpol.interactions.message_org(
|
||||
self.initiator, self.org.id,
|
||||
"Consent reached: removing org " ..
|
||||
self.data.child_to_remove.name)
|
||||
self.data.child_to_remove:delete()
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
modpol.modules.remove_child_consent = remove_child_consent
|
64
modpol_core/modules/remove_member_consent.lua
Normal file
64
modpol_core/modules/remove_member_consent.lua
Normal file
@ -0,0 +1,64 @@
|
||||
--- remove_member_consent
|
||||
-- @module remove_member_consent
|
||||
|
||||
local remove_member_consent = {
|
||||
name = "Remove a member (consent)",
|
||||
slug = "remove_member_consent",
|
||||
desc = "Removes org member with consent of other members"
|
||||
}
|
||||
|
||||
remove_member_consent.data = {
|
||||
member_to_remove = "",
|
||||
result = nil
|
||||
}
|
||||
|
||||
remove_member_consent.config = {
|
||||
}
|
||||
|
||||
function remove_member_consent:initiate(result)
|
||||
-- Abort if in root org
|
||||
if self.org == modpol.instance then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Members cannot be removed from the root org")
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
else -- proceed if not root
|
||||
self.data.result = result
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator,
|
||||
"Which member of org "..self.org.name..
|
||||
" do you want to remove?",
|
||||
self.org.members,
|
||||
function(input)
|
||||
self.data.member_to_remove = input
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Remove "..input..
|
||||
" from org "..self.org.name.."?",
|
||||
votes_required = #self.org.members - 1
|
||||
},
|
||||
function()
|
||||
self:complete()
|
||||
end)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function remove_member_consent:complete()
|
||||
modpol.interactions.message_org(
|
||||
self.initiator, self.org.id,
|
||||
"Consent reached: removing "..
|
||||
self.data.member_to_remove..
|
||||
" from org "..self.org.name)
|
||||
self.org:remove_member(self.data.member_to_remove)
|
||||
self.org:delete_process(self.id)
|
||||
if self.data.result then self.data.result() end
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.remove_member_consent = remove_member_consent
|
33
modpol_core/modules/remove_org.lua
Normal file
33
modpol_core/modules/remove_org.lua
Normal file
@ -0,0 +1,33 @@
|
||||
--- @module Remove Org
|
||||
-- A simple module that removes an org.
|
||||
|
||||
|
||||
--- Main module table
|
||||
remove_org = {
|
||||
name = "Remove this org",
|
||||
slug = "remove_org",
|
||||
desc = "Eliminates the org and all child orgs."
|
||||
}
|
||||
|
||||
remove_org.config = {}
|
||||
remove_org.data = {}
|
||||
|
||||
--- Initiate function
|
||||
-- @function initiate
|
||||
function remove_org:initiate(result)
|
||||
if self.org == modpol.instance then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Cannot remove the root org")
|
||||
else
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,self.org.id,
|
||||
"Removing org: "..self.org.name)
|
||||
self.org:delete()
|
||||
modpol.interactions.dashboard(self.initiator)
|
||||
-- call result function
|
||||
end
|
||||
if result then result() end
|
||||
end
|
||||
|
||||
modpol.modules.remove_org = remove_org
|
52
modpol_core/modules/remove_org_consent.lua
Normal file
52
modpol_core/modules/remove_org_consent.lua
Normal file
@ -0,0 +1,52 @@
|
||||
--- Remove org (consent)
|
||||
-- A simple module that calls a consent process on an org to remove it.
|
||||
-- Depends on the Consent module.
|
||||
|
||||
local remove_org_consent = {
|
||||
name = "Remove this org (consent)",
|
||||
slug = "remove_org_consent",
|
||||
desc = "Removes an org if all members consent."
|
||||
}
|
||||
|
||||
remove_org_consent.data = {
|
||||
result = nil
|
||||
}
|
||||
|
||||
remove_org_consent.config = {
|
||||
}
|
||||
|
||||
function remove_org_consent:initiate(result)
|
||||
if self.org == modpol.instance then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Cannot remove root org")
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
else
|
||||
self.data.result = result
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Remove org " .. self.org.name .. "?",
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function ()
|
||||
self:complete()
|
||||
end
|
||||
)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
end
|
||||
end
|
||||
|
||||
function remove_org_consent:complete()
|
||||
modpol.interactions.message_org(
|
||||
self.initiator, self.org.id,
|
||||
"Consent reached: removing org " .. self.org.name)
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
self.org:delete()
|
||||
end
|
||||
|
||||
modpol.modules.remove_org_consent = remove_org_consent
|
77
modpol_core/modules/rename_org_consent.lua
Normal file
77
modpol_core/modules/rename_org_consent.lua
Normal file
@ -0,0 +1,77 @@
|
||||
--- Rename org (consent)
|
||||
-- A simple module that calls a consent process on an org to rename it.
|
||||
-- Depends on the Consent module.
|
||||
|
||||
local rename_org_consent = {
|
||||
name = "Rename this org (consent)",
|
||||
slug = "rename_org_consent",
|
||||
desc = "Renames an org if all members consent."
|
||||
}
|
||||
|
||||
rename_org_consent.data = {
|
||||
result = nil,
|
||||
new_name = nil
|
||||
}
|
||||
|
||||
rename_org_consent.config = {
|
||||
}
|
||||
|
||||
function rename_org_consent:initiate(result)
|
||||
modpol.interactions.text_query(
|
||||
self.initiator,"New org name: ",
|
||||
function(input)
|
||||
if input == "" then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"No name entered for child org")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
self.org:delete_process(self.id)
|
||||
if result then result() end
|
||||
return
|
||||
elseif modpol.orgs.get_org(input) then
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Org name already in use")
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
self.org:delete_process(self.id)
|
||||
if result then result() end
|
||||
return
|
||||
end
|
||||
self.data.new_name = input
|
||||
modpol.interactions.message(
|
||||
self.initiator,
|
||||
"Proposed to change name of org " ..
|
||||
self.org.name .. " to " .. input)
|
||||
-- initiate consent process
|
||||
self.org:call_module(
|
||||
"consent",
|
||||
self.initiator,
|
||||
{
|
||||
prompt = "Change name of org " ..
|
||||
self.org.name .. " to " .. input .. "?",
|
||||
votes_required = #self.org.members
|
||||
},
|
||||
function()
|
||||
self:complete()
|
||||
end
|
||||
)
|
||||
modpol.interactions.org_dashboard(
|
||||
self.initiator, self.org.name)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
function rename_org_consent:complete()
|
||||
modpol.interactions.message_org(
|
||||
self.initiator,
|
||||
self.org.name,
|
||||
"Changing name of org " .. self.org.name ..
|
||||
" to " .. self.data.new_name)
|
||||
self.org.name = self.data.new_name
|
||||
if self.data.result then self.data.result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
modpol.modules.rename_org_consent = rename_org_consent
|
54
modpol_core/modules/template.lua
Normal file
54
modpol_core/modules/template.lua
Normal file
@ -0,0 +1,54 @@
|
||||
--- module_template
|
||||
-- @module module_template
|
||||
|
||||
--- (Required): data table containing name and description of the module
|
||||
-- @field name "Human-readable name (parens OK, no brackets)"
|
||||
-- @field slug "Same as module class name"
|
||||
-- @field desc "Description of the module"
|
||||
-- @field hide "Whether this is a hidden utility module"
|
||||
local module_template = {
|
||||
name = "Module Human-Readable Name",
|
||||
slug = "template",
|
||||
desc = "Description of the module",
|
||||
hide = false;
|
||||
}
|
||||
|
||||
--- (Required) Data for module
|
||||
-- Variables that module uses during the course of a process
|
||||
-- Can be blank
|
||||
module_template.data = {
|
||||
}
|
||||
|
||||
--- (Required): config for module
|
||||
-- Defines the input parameters to the module initiate function.
|
||||
-- Can be blank
|
||||
-- When calling a module from within another module,
|
||||
-- variables not defined in config will be ignored.
|
||||
-- Default values set in config can be overridden
|
||||
-- @field field_1 ex: votes_required, default = 5
|
||||
-- @field field_2 ex: voting_type, default = "majority"
|
||||
module_template.config = {
|
||||
field_1 = 5
|
||||
field_2 = "majority"
|
||||
}
|
||||
|
||||
--- (Required): initiate function
|
||||
-- Modules have access to the following instance variables:
|
||||
-- <li><code>self.org</code> (the org the module was called in),</li>
|
||||
-- <li><code>self.initiator</code> (the user that callced the module),</li>
|
||||
-- <li><code>self.id</code> (the process id of the module instance)</li>
|
||||
-- @param result (optional) Callback if this module is embedded in other modules
|
||||
-- @function initiate
|
||||
function module_template:initiate(result)
|
||||
-- call interaction functions here!
|
||||
|
||||
-- concluding functions:
|
||||
-- call these wherever process might end;
|
||||
-- may need to put result in self.data.result
|
||||
-- if process ends in another function
|
||||
if result then result() end
|
||||
self.org:delete_process(self.id)
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.module_template = module_template
|
@ -1,3 +1,6 @@
|
||||
--- Orgs: Base
|
||||
-- Basic functions for orgs
|
||||
|
||||
modpol.orgs = modpol.orgs or
|
||||
{
|
||||
count = 1,
|
||||
@ -11,14 +14,8 @@ function temp_org()
|
||||
return {
|
||||
id = nil,
|
||||
name = nil,
|
||||
policies = {
|
||||
add_org={process_type='consent', must_be_member=false},
|
||||
delete={process_type='consent', must_be_member=false},
|
||||
add_member={process_type='consent', must_be_member=false},
|
||||
remove_member={process_type='consent', must_be_member=false}
|
||||
},
|
||||
modules = modpol.copy_table(modpol.modules),
|
||||
processes = {},
|
||||
requests = {},
|
||||
pending = {},
|
||||
members = {},
|
||||
parent = nil,
|
||||
@ -177,7 +174,8 @@ function modpol.orgs:add_org(name, user)
|
||||
child_org.id = modpol.orgs.count
|
||||
child_org.name = name
|
||||
child_org.parent = self.id
|
||||
child_org.processes = self.processes
|
||||
child_org.processes = {}
|
||||
child_org.modules = self.modules
|
||||
|
||||
setmetatable(child_org, modpol.orgs)
|
||||
|
101
modpol_core/orgs/process.lua
Normal file
101
modpol_core/orgs/process.lua
Normal file
@ -0,0 +1,101 @@
|
||||
--- Process functions for orgs
|
||||
|
||||
function modpol.orgs:call_module(module_slug, initiator, config, result)
|
||||
if not modpol.modules[module_slug] then
|
||||
modpol.ocutil.log('Error in ' .. self.name .. ':call_module -> module "' .. module_slug .. '" not found')
|
||||
return
|
||||
end
|
||||
|
||||
local empty_index = nil
|
||||
-- linear search for empty process slots (lazy deletion)
|
||||
for k, v in ipairs(self.processes) do
|
||||
if v == 'deleted' then
|
||||
empty_index = k
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
local index
|
||||
-- attempts to fill empty spots in list, otherwise appends to end
|
||||
if empty_index then
|
||||
index = empty_index
|
||||
else
|
||||
index = #self.processes + 1
|
||||
end
|
||||
|
||||
local module = modpol.modules[module_slug]
|
||||
|
||||
-- sets default values for undeclared config variables
|
||||
if #module.config > 0 then
|
||||
for k, v in pairs(module.config) do
|
||||
if config[k] == nil then
|
||||
config[k] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- setting default params
|
||||
local new_process = {
|
||||
metatable = {__index = module},
|
||||
initiator = initiator,
|
||||
org = self,
|
||||
id = index,
|
||||
config = config,
|
||||
data = module.data,
|
||||
slug = module_slug
|
||||
}
|
||||
|
||||
setmetatable(new_process, new_process.metatable)
|
||||
|
||||
self.processes[index] = new_process
|
||||
self.processes[index]:initiate(result)
|
||||
|
||||
return index
|
||||
end
|
||||
|
||||
function modpol.orgs:delete_process(id)
|
||||
self.processes[id] = 'deleted'
|
||||
end
|
||||
|
||||
function modpol.orgs:add_pending_action(process_id, user, callback)
|
||||
self.pending[user] = self.pending[user] or {}
|
||||
self.pending[user][process_id] = callback
|
||||
modpol.interactions.message(
|
||||
user, "New pending action in org "..self.name)
|
||||
end
|
||||
|
||||
function modpol.orgs:remove_pending_action(process_id, user)
|
||||
if self.pending[user] then
|
||||
self.pending[user][process_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function modpol.orgs:wipe_pending_actions(process_id)
|
||||
for user in pairs(self.pending) do
|
||||
self.pending[user][process_id] = nil
|
||||
end
|
||||
end
|
||||
|
||||
function modpol.orgs:has_pending_actions(user)
|
||||
-- next() will return the next pair in a table
|
||||
-- if next() returns nil, the table is empty
|
||||
if not self.pending[user] then
|
||||
return false
|
||||
else
|
||||
if not next(self.pending[user]) then
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function modpol.orgs:interact(process_id, user)
|
||||
local process = self.processes[process_id]
|
||||
if self.pending[user] then
|
||||
local callback = self.pending[user][process_id]
|
||||
if callback then
|
||||
process[callback](process, user)
|
||||
end
|
||||
end
|
||||
end
|
13
modpol_core/tests/nested_module_test.lua
Normal file
13
modpol_core/tests/nested_module_test.lua
Normal file
@ -0,0 +1,13 @@
|
||||
dofile('../modpol.lua');
|
||||
|
||||
modpol.orgs.reset()
|
||||
|
||||
test_org = modpol.instance:add_org('test_org', 'luke')
|
||||
test_org:add_member('nathan')
|
||||
|
||||
print(table.concat(test_org:list_members(), ", "))
|
||||
|
||||
test_org:call_module(
|
||||
"join_org_consent",
|
||||
"paul"
|
||||
)
|
15
modpol_core/tests/new_module_test.lua
Normal file
15
modpol_core/tests/new_module_test.lua
Normal file
@ -0,0 +1,15 @@
|
||||
dofile('../modpol.lua');
|
||||
|
||||
modpol.orgs.reset()
|
||||
|
||||
test_org = modpol.instance:add_org('test_org', 'luke')
|
||||
test_org:add_member('nathan')
|
||||
|
||||
print(table.concat(test_org:list_members(), ", "))
|
||||
|
||||
-- modpol.modules.join_org.initiate("paul", test_org)
|
||||
|
||||
test_org:call_module(
|
||||
"join_org_class",
|
||||
"paul"
|
||||
)
|
9
modpol_core/util/misc.lua
Normal file
9
modpol_core/util/misc.lua
Normal file
@ -0,0 +1,9 @@
|
||||
--- @function modpol.copy_table
|
||||
-- Returns a copy of the table inputted
|
||||
function modpol.copy_table(t)
|
||||
local t2 = {}
|
||||
for k,v in pairs(t) do
|
||||
t2[k] = v
|
||||
end
|
||||
return t2
|
||||
end
|
@ -97,7 +97,7 @@ modpol.ocutil.log = function (s)
|
||||
|
||||
if modpol.ocutil.logdir ~= nil and
|
||||
modpol.ocutil.logname ~= nil then
|
||||
s = s .. "\n" -- Add trailing newline
|
||||
s = s .. "\n" -- Add trailing newline
|
||||
|
||||
local logpath = modpol.ocutil.logdir .. "/" .. modpol.ocutil.logname
|
||||
modpol.ocutil.file_append (logpath, s)
|
@ -1,6 +1,4 @@
|
||||
--call all files in this directory
|
||||
|
||||
|
||||
--- Script for loading Minetest files
|
||||
|
||||
-- ===================================================================
|
||||
-- Minetest Specific Overrides of modpol functions
|
||||
@ -9,32 +7,32 @@
|
||||
|
||||
local localdir = minetest.get_modpath("modpol") .. "/modpol_minetest"
|
||||
|
||||
--Users
|
||||
dofile (localdir .. "/overrides/users/users.lua")
|
||||
|
||||
--orgs
|
||||
--dofile (localdir .. "/overrides/orgs/orgs.lua")
|
||||
|
||||
--interactions
|
||||
dofile (localdir .. "/overrides/interactions/interactions.lua")
|
||||
|
||||
-- messaging functions
|
||||
-- dofile (localdir .. "/overrides/processes/processes.lua")
|
||||
|
||||
--overrides
|
||||
dofile (localdir .. "/overrides/interactions.lua")
|
||||
|
||||
--testing command for "singleplayer"
|
||||
function modpol.msg(text)
|
||||
modpol.interactions.message("singleplayer",text)
|
||||
end
|
||||
|
||||
-- ===================================================================
|
||||
-- Minetest Chatcommands
|
||||
-- ===================================================================
|
||||
dofile (localdir .. "/chatcommands/chatcommands.lua")
|
||||
dofile (localdir .. "/chatcommands.lua")
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- Minetest Specific code
|
||||
-- Minetest-specific modules
|
||||
-- ===================================================================
|
||||
|
||||
dofile (localdir .. "/modules/priv_to_org.lua")
|
||||
|
||||
-- orgs
|
||||
-- ===================
|
||||
-- ===================================================================
|
||||
-- Minetest-specific code
|
||||
-- ===================================================================
|
||||
|
||||
dofile (localdir .. "/orgs/instance.lua") --add players to the instance when they join.
|
||||
--add members to the instance, if they are not already there.
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local p_name = player:get_player_name()
|
||||
modpol.instance:add_member(p_name)
|
||||
end)
|
||||
|
@ -15,10 +15,10 @@ regchat = function(name, command_table)
|
||||
end
|
||||
|
||||
-- ===================================================================
|
||||
-- /modpol
|
||||
-- /mp
|
||||
-- Presents a menu of options to users
|
||||
regchat(
|
||||
"modpol", {
|
||||
"mp", {
|
||||
privs = {},
|
||||
func = function(user)
|
||||
modpol.interactions.dashboard(user)
|
||||
@ -26,14 +26,17 @@ regchat(
|
||||
})
|
||||
|
||||
-- ===================================================================
|
||||
-- /reset
|
||||
-- For testing only
|
||||
-- /mptest
|
||||
-- For testing only, accessible to admin users
|
||||
-- Clears the system and recreates instance with all players
|
||||
-- opens dashboard too for fun.
|
||||
regchat(
|
||||
"reset", {
|
||||
privs = {},
|
||||
"mptest", {
|
||||
privs = {privs=true},
|
||||
func = function(user)
|
||||
modpol.orgs.reset();
|
||||
modpol.orgs.reset()
|
||||
modpol.instance:add_member(user)
|
||||
modpol.interactions.dashboard(user)
|
||||
return true, "Reset orgs"
|
||||
end,
|
||||
})
|
||||
@ -71,38 +74,3 @@ regchat(
|
||||
end
|
||||
})
|
||||
|
||||
-- ===================================================================
|
||||
-- /listplayers
|
||||
regchat(
|
||||
"listplayers", {
|
||||
privs = {},
|
||||
func = function(user)
|
||||
local result = table.concat(modpol.list_users(),", ")
|
||||
return true, "All players: " .. result
|
||||
end,
|
||||
})
|
||||
|
||||
-- ===================================================================
|
||||
-- /joinorg
|
||||
regchat(
|
||||
"joinorg", {
|
||||
privs = {},
|
||||
func = function(user, param)
|
||||
local org = modpol.orgs.get_org(param)
|
||||
local success, result = org:add_member(user)
|
||||
return true, result
|
||||
end,
|
||||
})
|
||||
|
||||
|
||||
-- ===================================================================
|
||||
-- /pollself [question]
|
||||
-- asks the user a question specified in param
|
||||
regchat(
|
||||
"pollself", {
|
||||
privs = {},
|
||||
func = function(user, param)
|
||||
modpol.interactions.binary_poll_user(user, param)
|
||||
return true, result
|
||||
end,
|
||||
})
|
48
modpol_minetest/modules/priv_to_org.lua
Normal file
48
modpol_minetest/modules/priv_to_org.lua
Normal file
@ -0,0 +1,48 @@
|
||||
--- Set privilege to org members
|
||||
-- @module priv_to_org
|
||||
-- Allows initiator to grant a priv they have to all members of an org
|
||||
|
||||
local priv_to_org = {
|
||||
name = "Set privilege to org members",
|
||||
slug = "priv_to_org",
|
||||
desc = "Allows initiator to grant a priv they have to all members of an org"
|
||||
}
|
||||
|
||||
priv_to_org.data = {
|
||||
}
|
||||
|
||||
priv_to_org.config = {
|
||||
}
|
||||
|
||||
--- (Required): initiate function
|
||||
-- @param result (optional) Callback if this module is embedded in other modules
|
||||
-- @function initiate
|
||||
function priv_to_org:initiate(result)
|
||||
local player_privs = minetest.get_player_privs(self.initiator)
|
||||
-- construct table for display
|
||||
local player_privs_table = {"View..."}
|
||||
for k,v in pairs(player_privs) do
|
||||
if player_privs[k] then
|
||||
table.insert(player_privs_table,k)
|
||||
end
|
||||
end
|
||||
modpol.interactions.dropdown_query(
|
||||
self.initiator,
|
||||
"Which privilege do you want to share with members of "..self.org.name.."?",
|
||||
player_privs_table,
|
||||
function(input)
|
||||
for i,member in ipairs(self.org.members) do
|
||||
local member_privs = minetest.get_player_privs(member)
|
||||
member_privs[input] = true
|
||||
minetest.set_player_privs(member, member_privs)
|
||||
end
|
||||
local message = self.initiator .. " added " .. input ..
|
||||
" privilege to all members of " .. self.org.name
|
||||
modpol.interactions.message_org(self.initiator, self.org.id, message)
|
||||
end)
|
||||
-- call result function
|
||||
if result then result() end
|
||||
end
|
||||
|
||||
--- (Required) Add to module table
|
||||
modpol.modules.priv_to_org = priv_to_org
|
@ -1,6 +0,0 @@
|
||||
--add members to the instance, if they are not already there.
|
||||
|
||||
minetest.register_on_joinplayer(function(player)
|
||||
local p_name = player:get_player_name()
|
||||
modpol.instance:add_member(p_name)
|
||||
end)
|
@ -47,23 +47,19 @@ function modpol.interactions.dashboard(user)
|
||||
-- to add: nested orgs map
|
||||
local all_orgs = modpol.orgs.list_all()
|
||||
local user_orgs = modpol.orgs.user_orgs(user)
|
||||
local all_users = modpol.list_users()
|
||||
local all_users = modpol.instance:list_members()
|
||||
-- set up formspec
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
"size[10,8]",
|
||||
"label[0.5,0.5;MODPOL DASHBOARD]",
|
||||
"size[10,6]",
|
||||
"label[0.5,0.5;M O D U L A R P O L I T I C S]",
|
||||
"label[0.5,2;All orgs:]",
|
||||
"dropdown[2,1.5;5,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
|
||||
"dropdown[2,1.5;7,0.8;all_orgs;"..formspec_list(all_orgs)..";;]",
|
||||
"label[0.5,3;Your orgs:]",
|
||||
"dropdown[2,2.5;5,0.8;user_orgs;"..formspec_list(user_orgs)..";;]",
|
||||
"dropdown[2,2.5;7,0.8;user_orgs;"..formspec_list(user_orgs)..";;]",
|
||||
"label[0.5,4;All users:]",
|
||||
"dropdown[2,3.5;5,0.8;all_users;"..formspec_list(all_users)..";;]",
|
||||
"button[0.5,7;1,0.8;test_poll;Test poll]",
|
||||
"button[2,7;1,0.8;add_org;Add org]",
|
||||
"button[3.5,7;1.5,0.8;remove_org;Remove org]",
|
||||
"button[5.5,7;1.5,0.8;reset_orgs;Reset orgs]",
|
||||
"button_exit[8.5,7;1,0.8;close;Close]",
|
||||
"dropdown[2,3.5;7,0.8;all_users;"..formspec_list(all_users)..";;]",
|
||||
"button_exit[8.5,5;1,0.8;close;Close]",
|
||||
}
|
||||
local formspec_string = table.concat(formspec, "")
|
||||
-- present to player
|
||||
@ -75,26 +71,7 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
local pname = player:get_player_name()
|
||||
if nil then
|
||||
-- buttons first
|
||||
elseif fields.test_poll then
|
||||
-- FOR TESTING PURPOSES ONLY
|
||||
modpol.interactions.text_query(
|
||||
pname,"Poll question:",
|
||||
function(input)
|
||||
modpol.interactions.binary_poll_user(
|
||||
pname, input,
|
||||
function(vote)
|
||||
modpol.interactions.message(
|
||||
pname, pname .. " voted " .. vote)
|
||||
end)
|
||||
end)
|
||||
elseif fields.add_org then
|
||||
modpol.interactions.add_org(pname, 1)
|
||||
elseif fields.remove_org then
|
||||
modpol.interactions.remove_org(pname)
|
||||
elseif fields.reset_orgs then
|
||||
modpol.orgs.reset()
|
||||
modpol.instance:add_member(pname)
|
||||
modpol.interactions.dashboard(pname)
|
||||
-- none here right now!
|
||||
|
||||
-- Put all dropdowns at the end
|
||||
elseif fields.close then
|
||||
@ -108,86 +85,84 @@ end)
|
||||
|
||||
|
||||
-- Function: modpol.interactions.org_dashboard
|
||||
-- Params: user (string), org_name (string)
|
||||
-- Params: user (string), org_string (string or num)
|
||||
-- Output: Displays a menu of org-specific commands to the user
|
||||
function modpol.interactions.org_dashboard(user, org_name)
|
||||
function modpol.interactions.org_dashboard(user, org_string)
|
||||
-- prepare data
|
||||
local org = modpol.orgs.get_org(org_name)
|
||||
local org = modpol.orgs.get_org(org_string)
|
||||
if not org then return nil end
|
||||
local is_member = org:has_member(user)
|
||||
local membership_toggle = function()
|
||||
local toggle_code = ""
|
||||
if is_member then
|
||||
toggle_code = toggle_code
|
||||
..minetest.formspec_escape("leave")..";"
|
||||
..minetest.formspec_escape("Leave").."]"
|
||||
else
|
||||
toggle_code = toggle_code
|
||||
..minetest.formspec_escape("join")..";"
|
||||
..minetest.formspec_escape("Join").."]"
|
||||
|
||||
local function membership_toggle(org_display)
|
||||
local current_org = modpol.orgs.get_org(org_display)
|
||||
if current_org then
|
||||
if current_org:has_member(user) then
|
||||
return " (member)"
|
||||
end
|
||||
end
|
||||
return toggle_code
|
||||
return ""
|
||||
end
|
||||
|
||||
-- identify parent
|
||||
local parent = modpol.orgs.get_org(org.parent)
|
||||
if parent then parent = parent.name
|
||||
else parent = "none" end
|
||||
|
||||
-- prepare children menu
|
||||
local children = {"View..."}
|
||||
local children = {}
|
||||
for k,v in ipairs(org.children) do
|
||||
local this_child = modpol.orgs.get_org(v)
|
||||
table.insert(children, this_child.name)
|
||||
end
|
||||
-- prepare policies menu
|
||||
local policies = {"View..."}
|
||||
for k,v in pairs(org.policies) do
|
||||
table.insert(policies, k .. ": " ..
|
||||
org.policies[k].process_type)
|
||||
end
|
||||
table.insert(policies, "Add policy")
|
||||
-- prepare processes menu
|
||||
local processes = {"View..."}
|
||||
for k,v in ipairs(org.processes) do
|
||||
local this_request = org.requests[v.request_id]
|
||||
if type(this_request) == "table" then
|
||||
local active = ''
|
||||
if org.pending[user] then
|
||||
if org.pending[user][v.id] then
|
||||
active = '*'
|
||||
end
|
||||
table.sort(children)
|
||||
table.insert(children,1,"View...")
|
||||
|
||||
-- prepare modules menu
|
||||
local modules = {}
|
||||
if org.modules then
|
||||
for k,v in pairs(org.modules) do
|
||||
if not v.hide then -- hide utility modules
|
||||
local module_entry = v.name..
|
||||
" ["..v.slug.."]"
|
||||
table.insert(modules, module_entry)
|
||||
end
|
||||
local req_str = "[" .. v.id .. "] " ..
|
||||
active .. this_request.type
|
||||
if this_request.params[1] then
|
||||
req_str = req_str .. ": " ..
|
||||
table.concat(this_request.params, ", ")
|
||||
end
|
||||
table.insert(processes, req_str)
|
||||
end
|
||||
end
|
||||
table.sort(modules)
|
||||
table.insert(modules,1,"View...")
|
||||
|
||||
-- prepare pending menu
|
||||
local pending = {}
|
||||
local num_pending = 0
|
||||
if org.pending[user] then
|
||||
for k,v in pairs(org.pending[user]) do
|
||||
local pending_string = org.processes[k].name
|
||||
.." ["..k.."]"
|
||||
table.insert(pending, pending_string)
|
||||
num_pending = num_pending + 1
|
||||
end
|
||||
end
|
||||
table.sort(pending)
|
||||
table.insert(pending,1,"View...")
|
||||
|
||||
-- set player context
|
||||
local user_context = {}
|
||||
user_context["current_org"] = org_name
|
||||
user_context["current_org"] = org.name
|
||||
_contexts[user] = user_context
|
||||
-- set up formspec
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
"size[10,8]",
|
||||
"label[0.5,0.5;Org: "..
|
||||
minetest.formspec_escape(org_name).."]",
|
||||
"label[0.5,1;Parent: "..parent.."]",
|
||||
"button[8.5,0.5;1,0.8;"..membership_toggle(),
|
||||
minetest.formspec_escape(org.name)..membership_toggle(org.name).."]",
|
||||
"label[0.5,1;Parent: "..parent..membership_toggle(parent).."]",
|
||||
"label[0.5,2;Members:]",
|
||||
"dropdown[2,1.5;5,0.8;user_orgs;"..formspec_list(org.members)..";;]",
|
||||
"dropdown[2,1.5;7,0.8;user_orgs;"..formspec_list(org.members)..";;]",
|
||||
"label[0.5,3;Children:]",
|
||||
"dropdown[2,2.5;5,0.8;children;"..formspec_list(children)..";;]",
|
||||
"label[0.5,4;Policies:]",
|
||||
"dropdown[2,3.5;5,0.8;policies;"..formspec_list(policies)..";;]",
|
||||
"label[0.5,5;Processes:]",
|
||||
"dropdown[2,4.5;5,0.8;processes;"..formspec_list(processes)..";;]",
|
||||
"button[0.5,7;1,0.8;test_poll;Test poll]",
|
||||
"button[2,7;1,0.8;add_child;Add child]",
|
||||
"button[3.5,7;1.5,0.8;remove_org;Remove org]",
|
||||
"dropdown[2,2.5;7,0.8;children;"..formspec_list(children)..";;]",
|
||||
"label[0.5,4;Modules:]",
|
||||
"dropdown[2,3.5;7,0.8;modules;"..formspec_list(modules)..";;]",
|
||||
"label[0.5,5;Pending ("..num_pending.."):]",
|
||||
"dropdown[2,4.5;7,0.8;pending;"..formspec_list(pending)..";;]",
|
||||
"button[8.5,7;1,0.8;back;Back]",
|
||||
}
|
||||
local formspec_string = table.concat(formspec, "")
|
||||
@ -199,72 +174,44 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
if formname == "modpol:org_dashboard" then
|
||||
local pname = player:get_player_name()
|
||||
local org = modpol.orgs.get_org(_contexts[pname].current_org)
|
||||
if nil then
|
||||
elseif fields.join then
|
||||
local new_request = {
|
||||
user = pname,
|
||||
type = "add_member",
|
||||
params = {pname}
|
||||
}
|
||||
org:make_request(new_request)
|
||||
modpol.interactions.org_dashboard(pname,org.name)
|
||||
elseif fields.leave then
|
||||
org:remove_member(pname)
|
||||
-- just confirm the org still exists:
|
||||
if not org then
|
||||
modpol.interactions.message(pname, "Org no longer exists")
|
||||
modpol.interactions.dashboard(pname)
|
||||
elseif fields.test_poll then
|
||||
modpol.interactions.binary_poll_org(
|
||||
pname, org.id,
|
||||
function(input)
|
||||
modpol.interactions.message_org(
|
||||
pname,
|
||||
org.id,
|
||||
"New response: " .. input)
|
||||
end)
|
||||
elseif fields.add_child then
|
||||
modpol.interactions.text_query(
|
||||
pname, "Child org name:",
|
||||
function(input)
|
||||
local new_request = {
|
||||
user = pname,
|
||||
type = "add_org",
|
||||
params = {input}
|
||||
}
|
||||
org:make_request(new_request)
|
||||
modpol.interactions.message(pname,"requested")
|
||||
modpol.interactions.org_dashboard(
|
||||
pname,org.name)
|
||||
end)
|
||||
elseif fields.remove_org then
|
||||
local new_request = {
|
||||
user = pname,
|
||||
type = "delete",
|
||||
params = {}
|
||||
}
|
||||
org:make_request(new_request)
|
||||
modpol.interactions.org_dashboard(pname,org.name)
|
||||
return end
|
||||
-- okay, onward
|
||||
if nil then
|
||||
elseif fields.back then
|
||||
modpol.interactions.dashboard(pname)
|
||||
|
||||
-- Put all dropdowns at the end
|
||||
elseif fields.policies
|
||||
and fields.policies ~= "View..." then
|
||||
local policy
|
||||
if fields.policies == "Add policy" then
|
||||
policy = nil
|
||||
elseif fields.policies == "View..." then
|
||||
return
|
||||
else
|
||||
policy = string.match(fields.policies,"(%w+)%:")
|
||||
end
|
||||
modpol.interactions.policy_dashboard(
|
||||
pname, org.id, policy)
|
||||
elseif fields.processes
|
||||
and fields.processes ~= "View..." then
|
||||
local sel = string.match(fields.processes,"%[(%d)%]")
|
||||
local process = org.processes[tonumber(sel)]
|
||||
-- Receiving modules
|
||||
elseif fields.modules
|
||||
and fields.modules ~= "View..." then
|
||||
local module = string.match(
|
||||
fields.modules,"%[(.*)%]")
|
||||
modpol.interactions.binary_poll_user(
|
||||
pname,
|
||||
modpol.modules[module].name..":\n"..
|
||||
modpol.modules[module].desc.."\n"..
|
||||
"Proceed?",
|
||||
function(input)
|
||||
if input == "Yes" then
|
||||
org:call_module(module, pname)
|
||||
end
|
||||
end)
|
||||
|
||||
-- Receiving pending
|
||||
elseif fields.pending
|
||||
and fields.pending ~= "View..." then
|
||||
local pending = string.match(
|
||||
fields.pending,"%[(%d)%]")
|
||||
local process = org.processes[tonumber(pending)]
|
||||
if process then
|
||||
process:interact(pname)
|
||||
org:interact(process.id, pname)
|
||||
end
|
||||
|
||||
-- Children
|
||||
elseif fields.children
|
||||
and fields.children ~= "View..." then
|
||||
local org_name = fields.children
|
||||
@ -286,20 +233,21 @@ function modpol.interactions.policy_dashboard(
|
||||
end
|
||||
|
||||
|
||||
-- BASIC INTERACTION FUNCTIONS
|
||||
-- ===========================
|
||||
-- INTERACTION FUNCTIONS
|
||||
-- =====================
|
||||
|
||||
-- Function: modpol.interactions.message
|
||||
-- input: message (string)
|
||||
-- output
|
||||
-- input: user (string), message (string)
|
||||
-- output: displays message to specified user
|
||||
function modpol.interactions.message(user, message)
|
||||
minetest.chat_send_player(user, message)
|
||||
if message then
|
||||
minetest.chat_send_player(user, message)
|
||||
end
|
||||
end
|
||||
|
||||
-- Function: modpol.interactions.text_query
|
||||
-- Overrides function at modpol/interactions.lua
|
||||
-- input: user (string), query (string), func (function)
|
||||
-- func input: user input (string)
|
||||
-- output: Applies "func" to user input
|
||||
function modpol.interactions.text_query(user, query, func)
|
||||
-- set up formspec
|
||||
@ -311,7 +259,7 @@ function modpol.interactions.text_query(user, query, func)
|
||||
"button[0.5,2.5;1,0.8;yes;OK]",
|
||||
}
|
||||
local formspec_string = table.concat(formspec, "")
|
||||
-- present to players
|
||||
-- present to player
|
||||
minetest.show_formspec(user, "modpol:text_query", formspec_string)
|
||||
-- put func in _contexts
|
||||
if _contexts[user] == nil then _contexts[user] = {} end
|
||||
@ -341,6 +289,8 @@ end)
|
||||
-- func input: choice (string)
|
||||
-- output: calls func on user choice
|
||||
function modpol.interactions.dropdown_query(user, label, options, func)
|
||||
-- Add "View..." to the top of the list
|
||||
table.insert(options,1,"View...")
|
||||
-- set up formspec
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
@ -360,25 +310,28 @@ end
|
||||
minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
if formname == "modpol:dropdown_query" then
|
||||
local pname = player:get_player_name()
|
||||
if fields.cancel ~= "cancel" then
|
||||
if fields.cancel then
|
||||
minetest.close_formspec(pname, formname)
|
||||
elseif fields.input == "View..." then
|
||||
-- "View...", do nothing
|
||||
else
|
||||
local choice = fields.input
|
||||
local func = _contexts[pname]["dropdown_query_func"]
|
||||
if not choice then
|
||||
-- no choice, do nothing
|
||||
-- empty, do nothing
|
||||
elseif func then
|
||||
--not sure if we should close
|
||||
--breaks sequential dropdown_queries:
|
||||
--minetest.close_formspec(pname, formname)
|
||||
func(choice)
|
||||
else
|
||||
minetest.close_formspec(pname, formname)
|
||||
modpol.interactions.message(pname, "dropdown_query: " .. choice)
|
||||
end
|
||||
end
|
||||
minetest.close_formspec(pname, formname)
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
-- SECONDARY INTERACTIONS
|
||||
-- ======================
|
||||
|
||||
-- Function: modpol.binary_poll_user(user, question, function)
|
||||
-- Overrides function at modpol/interactions.lua
|
||||
-- Params: user (string), question (string), func (function)
|
||||
@ -388,12 +341,12 @@ function modpol.interactions.binary_poll_user(user, question, func)
|
||||
-- set up formspec
|
||||
local formspec = {
|
||||
"formspec_version[4]",
|
||||
"size[5,3]",
|
||||
"size[8,4]",
|
||||
"label[0.375,0.5;",minetest.formspec_escape(question), "]",
|
||||
"button[1,1.5;1,0.8;yes;Yes]",
|
||||
"button[2,1.5;1,0.8;no;No]",
|
||||
--TKTK can we enable text wrapping?
|
||||
--TKTK we could use scroll boxes to contain the text
|
||||
"button[1,2.5;1,0.8;yes;Yes]",
|
||||
"button[2,2.5;1,0.8;no;No]",
|
||||
--TODO can we enable text wrapping?
|
||||
--TODO we could use scroll boxes to contain the text
|
||||
}
|
||||
local formspec_string = table.concat(formspec, "")
|
||||
if _contexts[user] == nil then _contexts[user] = {} end
|
||||
@ -410,78 +363,9 @@ minetest.register_on_player_receive_fields(function (player, formname, fields)
|
||||
elseif fields.no then vote = fields.no
|
||||
end
|
||||
if vote then
|
||||
modpol.interactions.message(pname, "Responded " .. vote)
|
||||
local func = _contexts[pname]["binary_poll_func"]
|
||||
if func then func(vote) end
|
||||
end
|
||||
minetest.close_formspec(pname, formname)
|
||||
end
|
||||
end)
|
||||
|
||||
-- COMPLEX INTERACTIONS
|
||||
-- ====================
|
||||
|
||||
-- Function: modpol.interactions.message_org
|
||||
-- input: initiator (string), org_id (number), message (string)
|
||||
-- output: broadcasts message to all org members
|
||||
function modpol.interactions.message_org(initiator, org_id, message)
|
||||
local org = modpol.orgs.get_org(org_id)
|
||||
local users = org:list_members()
|
||||
for k,v in ipairs(users) do
|
||||
modpol.interactions.message(v, message)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
-- Function: modpol.interactions.binary_poll_org
|
||||
-- input: initator (user string), org_id (number)
|
||||
-- output: gets question from initiator, asks all org members, broadcasts answers
|
||||
-- TODO for testing. This should be implemented as a request.
|
||||
function modpol.interactions.binary_poll_org(initiator, org_id, func)
|
||||
local org = modpol.orgs.get_org(org_id)
|
||||
local users = org:list_members()
|
||||
modpol.interactions.text_query(
|
||||
initiator, "Yes/no poll question:",
|
||||
function(input)
|
||||
for k,v in ipairs(users) do
|
||||
modpol.interactions.binary_poll_user(v, input, func)
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
-- Function: modpol.interactions.add_org
|
||||
-- input: initator (user string), base_org_id (ID)
|
||||
-- output: interaction begins
|
||||
-- GODMODE
|
||||
function modpol.interactions.add_org(user, base_org_id)
|
||||
modpol.interactions.text_query(
|
||||
user,"Org name:",
|
||||
function(input)
|
||||
local base_org = modpol.orgs.get_org(1)
|
||||
local result = base_org:add_org(input, user)
|
||||
local message = input .. " created"
|
||||
modpol.interactions.message(user, message)
|
||||
modpol.interactions.dashboard(user)
|
||||
end)
|
||||
end
|
||||
|
||||
-- Function: modpol.interactions.remove_org
|
||||
-- input: initator (user string)
|
||||
-- output: interaction begins
|
||||
-- GODMODE
|
||||
function modpol.interactions.remove_org(user)
|
||||
-- start formspec
|
||||
local orgs_list = modpol.orgs.list_all()
|
||||
local label = "Choose an org to remove:"
|
||||
modpol.interactions.dropdown_query(
|
||||
user, label, orgs_list,
|
||||
function(input)
|
||||
if input then
|
||||
local target_org = modpol.orgs.get_org(input)
|
||||
local result = target_org:delete()
|
||||
local message = input .. " deleted"
|
||||
modpol.interactions.message(user, message)
|
||||
end
|
||||
modpol.interactions.dashboard(user)
|
||||
end)
|
||||
end
|
@ -1,21 +0,0 @@
|
||||
|
||||
-- ===================================================================
|
||||
-- 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(users,name)
|
||||
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
|
Loading…
x
Reference in New Issue
Block a user