Explorar o código

Merge branch 'modules-as-actions' into 'master'

Merge modules-as-action refactor to main!

See merge request medlabboulder/modpol!31
Nathan Schneider %!s(int64=2) %!d(string=hai) anos
pai
achega
6aa09f9fdd
Modificáronse 50 ficheiros con 1272 adicións e 1082 borrados
  1. 1 0
      GOVERNANCE.md
  2. 1 1
      LICENSE
  3. 48 21
      README.md
  4. 1 1
      description.txt
  5. 2 2
      init.lua
  6. 7 0
      login.lua
  7. 0 35
      modpol/DOCS/Predefinitions.txt
  8. 0 50
      modpol/DOCS/ledgers
  9. 0 137
      modpol/DOCS/orgs
  10. 0 16
      modpol/api.lua
  11. 0 7
      modpol/interactions/login.lua
  12. 0 98
      modpol/modules/consent.lua
  13. 0 0
      modpol/modules/defer_to.lua
  14. 0 272
      modpol/orgs/requests.lua
  15. 0 31
      modpol/tests/nested_functions.lua
  16. 0 29
      modpol/users/users.lua
  17. 23 0
      modpol_core/api.lua
  18. 59 50
      modpol_core/interactions/interactions.lua
  19. 12 3
      modpol_core/modpol.lua
  20. 75 0
      modpol_core/modules/add_child_org_consent.lua
  21. 171 0
      modpol_core/modules/change_modules.lua
  22. 56 0
      modpol_core/modules/consent.lua
  23. 39 0
      modpol_core/modules/join_org.lua
  24. 51 0
      modpol_core/modules/join_org_consent.lua
  25. 39 0
      modpol_core/modules/leave_org.lua
  26. 71 0
      modpol_core/modules/remove_child_consent.lua
  27. 64 0
      modpol_core/modules/remove_member_consent.lua
  28. 33 0
      modpol_core/modules/remove_org.lua
  29. 52 0
      modpol_core/modules/remove_org_consent.lua
  30. 77 0
      modpol_core/modules/rename_org_consent.lua
  31. 54 0
      modpol_core/modules/template.lua
  32. 6 8
      modpol_core/orgs/base.lua
  33. 101 0
      modpol_core/orgs/process.lua
  34. 0 0
      modpol_core/storage/storage-local.lua
  35. 13 0
      modpol_core/tests/nested_module_test.lua
  36. 15 0
      modpol_core/tests/new_module_test.lua
  37. 0 0
      modpol_core/tests/org_basic_test.lua
  38. 0 0
      modpol_core/tests/org_req_test.lua
  39. 9 0
      modpol_core/util/misc.lua
  40. 1 1
      modpol_core/util/ocutil/ocutil.lua
  41. 0 0
      modpol_core/util/serpent/LICENSE.txt
  42. 0 0
      modpol_core/util/serpent/README.md
  43. 0 0
      modpol_core/util/serpent/serpent-git.tar.bz2
  44. 0 0
      modpol_core/util/serpent/serpent.lua
  45. 18 20
      modpol_minetest/api.lua
  46. 10 42
      modpol_minetest/chatcommands.lua
  47. 48 0
      modpol_minetest/modules/priv_to_org.lua
  48. 0 6
      modpol_minetest/orgs/instance.lua
  49. 115 231
      modpol_minetest/overrides/interactions.lua
  50. 0 21
      modpol_minetest/overrides/users/users.lua

+ 1 - 0
modpol/DOCS/GOVERNANCE.md → GOVERNANCE.md

@@ -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.
 
 ---
 

+ 1 - 1
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

+ 48 - 21
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 dashboard with the command `/mp`.
 
-In the game, open the Modular Politics interface with the command `/modpol`.
+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:
+
+```
+$ lua[jit] login.lua
+```
+
+You can also interact with the interpreter by starting it this way:
 
 ```
-$ cd modpol/
-$ lua [or luajit]
-> dofile("modpol.lua")
+$ lua[jit]
+> dofile("login.lua")
 ```
 
-For a list of global functions and tables, use `modpol.menu()`.
+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 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).
 
-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).
+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
description.txt

@@ -1 +1 @@
-Provides an API for diverse governance mechanisms
+Provides a basis for diverse, user-configurable modules for in-game governance.

+ 2 - 2
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 - 0
login.lua

@@ -0,0 +1,7 @@
+dofile("modpol_core/modpol.lua")
+
+print("Log in as which user?")
+local username = io.read()
+
+print()
+modpol.interactions.dashboard(username) 

+ 0 - 35
modpol/DOCS/Predefinitions.txt

@@ -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. 
-

+ 0 - 50
modpol/DOCS/ledgers

@@ -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.

+ 0 - 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

+ 0 - 16
modpol/api.lua

@@ -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")

+ 0 - 7
modpol/interactions/login.lua

@@ -1,7 +0,0 @@
-dofile("../modpol.lua")
-
-print("Log in as which user?")
-local username = io.read()
-
-print()
-modpol.interactions.dashboard(username) 

+ 0 - 98
modpol/modules/consent.lua

@@ -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

+ 0 - 0
modpol/modules/defer_to.lua


+ 0 - 272
modpol/orgs/requests.lua

@@ -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
-

+ 0 - 31
modpol/tests/nested_functions.lua

@@ -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")
-

+ 0 - 29
modpol/users/users.lua

@@ -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 - 0
modpol_core/api.lua

@@ -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")

+ 59 - 50
modpol/interactions/interactions.lua → modpol_core/interactions/interactions.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)

+ 12 - 3
modpol/modpol.lua → modpol_core/modpol.lua

@@ -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,11 +66,18 @@ dofile (modpol.storage_file_path)
 -- If available, load persistent storage into active tables
 modpol.load_storage()
 
+
 -- ===================================================================
--- ModPol core features
+-- Modpol modules
 
-dofile (topdir .. "/api.lua")
+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 - 0
modpol_core/modules/add_child_org_consent.lua

@@ -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 - 0
modpol_core/modules/change_modules.lua

@@ -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 - 0
modpol_core/modules/consent.lua

@@ -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 - 0
modpol_core/modules/join_org.lua

@@ -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 - 0
modpol_core/modules/join_org_consent.lua

@@ -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 - 0
modpol_core/modules/leave_org.lua

@@ -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 - 0
modpol_core/modules/remove_child_consent.lua

@@ -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 - 0
modpol_core/modules/remove_member_consent.lua

@@ -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 - 0
modpol_core/modules/remove_org.lua

@@ -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 - 0
modpol_core/modules/remove_org_consent.lua

@@ -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 - 0
modpol_core/modules/rename_org_consent.lua

@@ -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 - 0
modpol_core/modules/template.lua

@@ -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

+ 6 - 8
modpol/orgs/base.lua → modpol_core/orgs/base.lua

@@ -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 - 0
modpol_core/orgs/process.lua

@@ -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

+ 0 - 0
modpol/storage/storage-local.lua → modpol_core/storage/storage-local.lua


+ 13 - 0
modpol_core/tests/nested_module_test.lua

@@ -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 - 0
modpol_core/tests/new_module_test.lua

@@ -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"
+)

+ 0 - 0
modpol/tests/org_basic_test.lua → modpol_core/tests/org_basic_test.lua


+ 0 - 0
modpol/tests/org_req_test.lua → modpol_core/tests/org_req_test.lua


+ 9 - 0
modpol_core/util/misc.lua

@@ -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

+ 1 - 1
modpol/util/ocutil/ocutil.lua → modpol_core/util/ocutil/ocutil.lua

@@ -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)

+ 0 - 0
modpol/util/serpent/LICENSE.txt → modpol_core/util/serpent/LICENSE.txt


+ 0 - 0
modpol/util/serpent/README.md → modpol_core/util/serpent/README.md


+ 0 - 0
modpol/util/serpent/serpent-git.tar.bz2 → modpol_core/util/serpent/serpent-git.tar.bz2


+ 0 - 0
modpol/util/serpent/serpent.lua → modpol_core/util/serpent/serpent.lua


+ 18 - 20
modpol_minetest/api.lua

@@ -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)

+ 10 - 42
modpol_minetest/chatcommands/chatcommands.lua → modpol_minetest/chatcommands.lua

@@ -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 - 0
modpol_minetest/modules/priv_to_org.lua

@@ -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

+ 0 - 6
modpol_minetest/orgs/instance.lua

@@ -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)

+ 115 - 231
modpol_minetest/overrides/interactions/interactions.lua → modpol_minetest/overrides/interactions.lua

@@ -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
-         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, ", ")
+   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
-         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

+ 0 - 21
modpol_minetest/overrides/users/users.lua

@@ -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