interactions.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495
  1. --- INTERACTIONS.LUA (CLI).
  2. -- User interaction functions for Modular Politics
  3. -- Called by modpol.lua
  4. -- @module modpol.interactions
  5. modpol.interactions = {}
  6. -- UTILITIES
  7. -- =========
  8. --- Output: returns a string listing a module's policies
  9. -- @function modpol.interactions.get_policy_string
  10. -- @param org (string or number) name or id for the org
  11. -- @param module_slug (string)
  12. -- @param sep (string) separator string
  13. function modpol.interactions.get_policy_string(
  14. org, module_slug, sep)
  15. local this_org = modpol.orgs.get_org(org)
  16. local this_module = modpol.modules[module_slug]
  17. local output = {}
  18. if modpol.util.num_pairs(this_module.config) > 0 then
  19. for k, v in pairs(this_module.config) do
  20. local this_policy = k .. " - " .. tostring(v)
  21. table.insert(output,this_policy)
  22. end
  23. end
  24. return "Policies:\n" .. table.concat(output, sep)
  25. end
  26. -- DASHBOARDS
  27. -- ==========
  28. --- Output: Display a menu of commands to the user
  29. -- @function modpol.interactions.dashboard
  30. -- @param user (string)
  31. -- Q: Should this return a menu of commands relevant to the specific user?
  32. -- TKTK currently just prints all of modpol---needs major improvement
  33. function modpol.interactions.dashboard(user)
  34. -- adds user to root org if not already in it
  35. if not modpol.instance:has_member(user) then
  36. modpol.instance:add_member(user)
  37. end
  38. local all_users = modpol.instance:list_members()
  39. print('\n-=< MODPOL DASHBOARD >=-')
  40. print('All orgs: (user orgs indicated by *)')
  41. for id, org in ipairs(modpol.orgs.array) do
  42. if type(org) == "table" then
  43. local indicator = ""
  44. if org:has_member(user) then indicator = "*" end
  45. print('['..id..'] '..indicator..org.name)
  46. end
  47. end
  48. -- pending list
  49. local user_pending = {}
  50. local user_pending_count = 0
  51. for k,v in ipairs(modpol.orgs.array) do
  52. if v.pending and v.pending[user] then
  53. if modpol.util.num_pairs(v.pending[user]) ~= 0 then
  54. table.insert(user_pending, v.name)
  55. user_pending_count = user_pending_count + 1
  56. end
  57. end
  58. end
  59. print('Pending actions in: '..table.concat(user_pending,', '))
  60. print('All users: ' .. table.concat(all_users, ', '))
  61. print()
  62. print("Commands: (O)rg, (U)ser, (R)eset, (Q)uit")
  63. local sel = io.read()
  64. if sel == "O" or sel == "o" then
  65. print('Access which org id?')
  66. sel = io.read()
  67. print()
  68. if modpol.orgs.array[tonumber(sel)] then
  69. local sel_org = modpol.orgs.array[tonumber(sel)].name
  70. modpol.interactions.org_dashboard(user, sel_org)
  71. else
  72. print("Org id not found")
  73. modpol.interactions.dashboard(user)
  74. end
  75. elseif sel == "U" or sel == "u" then
  76. print("Access which user?")
  77. sel = io.read()
  78. print()
  79. if modpol.instance:has_member(sel) then
  80. modpol.interactions.user_dashboard(
  81. user, sel,
  82. function()
  83. modpol.interactions.dashboard(user)
  84. end
  85. )
  86. else
  87. print("User name not found")
  88. modpol.interactions.dashboard(user)
  89. end
  90. elseif sel == "R" or sel == "r" then
  91. modpol.instance.members = {}
  92. modpol.orgs.reset()
  93. print("Orgs and users reset")
  94. modpol.interactions.dashboard(user)
  95. elseif sel == "Q" or "q" then
  96. return
  97. else
  98. print("Invalid input, try again")
  99. modpol.interactions.dashboard(user)
  100. end
  101. end
  102. --- Output: Displays a menu of org-specific commands to the user
  103. -- @function modpol.interactions.org_dashboard
  104. -- @param user (string)
  105. -- @param org_string (string or id)
  106. function modpol.interactions.org_dashboard(user, org_string)
  107. local org = modpol.orgs.get_org(org_string)
  108. if not org then return nil end
  109. -- identify parent
  110. local parent = ""
  111. if org.id == 1 then
  112. parent = "none"
  113. else
  114. parent = modpol.orgs.get_org(org.parent).name
  115. end
  116. -- identify children
  117. local children = {}
  118. for k,v in ipairs(org.children) do
  119. local this_child = modpol.orgs.get_org(v)
  120. table.insert(children, this_child.name)
  121. end
  122. -- prepare modules menu
  123. local modules = {}
  124. if org.modules then
  125. for k,v in pairs(org.modules) do
  126. if not v.hide then -- hide utility modules
  127. local module_entry = v.slug
  128. table.insert(modules, module_entry)
  129. end
  130. end
  131. end
  132. table.sort(modules)
  133. -- list pending
  134. local process_msg = #org.processes .. " total processes"
  135. if org.pending[user] then
  136. process_msg = process_msg .. " (" ..
  137. modpol.util.num_pairs(org.pending[user]) .. " pending)"
  138. else
  139. process_msg = process_msg .. " (0 pending)"
  140. end
  141. -- set up output
  142. print('\n-=< ORG DASHBOARD >=-')
  143. print("Org: " .. org.name)
  144. print("Parent: " .. parent)
  145. print("Members: " .. table.concat(org.members, ", "))
  146. print("Child orgs: " .. table.concat(children, ", "))
  147. print("Modules: " .. table.concat(modules, ", "))
  148. print("Pending: " .. process_msg)
  149. print()
  150. print("Commands: (M)odules, (P)ending, (B)ack")
  151. local sel = io.read()
  152. print()
  153. if sel == 'm' or sel == 'M' then
  154. print("Type module name: ")
  155. local module_sel = io.read()
  156. print()
  157. local module_result = false
  158. for k,v in ipairs(modules) do
  159. if v == module_sel then
  160. module_result = true
  161. end
  162. end
  163. local module = org.modules[module_sel]
  164. if module_result then
  165. modpol.interactions.binary_poll_user(
  166. user,
  167. module.name..":\n"..
  168. module.desc.."\n"..
  169. modpol.interactions.get_policy_string(
  170. org.name, module.slug, "\n")..
  171. "\n".."Proceed?",
  172. function(input)
  173. if input == "Yes" then
  174. org:call_module(module_sel, user)
  175. elseif input == "No" then
  176. modpol.interactions.org_dashboard(
  177. pname, org.id)
  178. end
  179. end)
  180. else
  181. print("Error: Module not found.")
  182. modpol.interactions.org_dashboard(user, org.id)
  183. end
  184. elseif sel == 'p' or sel == 'P' then
  185. local processes = {}
  186. print("All processes: (* indicates pending)")
  187. for i,v in ipairs(org.processes) do
  188. if v ~= "deleted" then
  189. local active = ''
  190. if org.pending[user] then
  191. if org.pending[user][v.id] then
  192. active = '*'
  193. end
  194. end
  195. print("["..v.id.."] "..v.slug..active)
  196. end
  197. end
  198. print()
  199. print("Interact with which one (use [id] number)?")
  200. local to_interact = io.read()
  201. local process = org.processes[tonumber(to_interact)]
  202. if not process then
  203. modpol.interactions.message(
  204. user, "Not a pending process")
  205. modpol.interactions.org_dashboard(user, org.id)
  206. return
  207. end
  208. if org:has_pending_actions(user) then
  209. if org.pending[user][process.id] then
  210. org:interact(process.id, user)
  211. end
  212. end
  213. elseif sel == 'b' or sel == 'B' then
  214. modpol.interactions.dashboard(user)
  215. else
  216. print("Command not found")
  217. modpol.interactions.org_dashboard(user, org.name)
  218. end
  219. end
  220. --- Function: modpol.interactions.user_dashboard
  221. -- Displays a dashboard about a particular user
  222. -- @param viewer Name of user viewing the dashboard (string)
  223. -- @param user Name of user being viewed (string)
  224. -- @param completion Optional function to call on Done button
  225. function modpol.interactions.user_dashboard(viewer, user, completion)
  226. local user_orgs = {}
  227. local user_modules = {}
  228. print("\n-=< USER DASHBOARD: "..user.." >=-")
  229. print("User's orgs:")
  230. for id, org in ipairs(modpol.orgs.array) do
  231. if type(org) == "table" then
  232. if org:has_member(user) then
  233. print(org.name)
  234. end
  235. end
  236. end
  237. print()
  238. print("Commands: (M)essage user, Enter when done")
  239. local sel = io.read()
  240. if sel == "M" or sel == "m" then
  241. modpol.interactions.message_user(
  242. viewer, user)
  243. completion()
  244. else
  245. completion()
  246. end
  247. end
  248. -- INTERACTION PRIMITIVES
  249. -- ======================
  250. --- Prints message to CLI.
  251. -- Buttons: message, done
  252. -- @function modpol.interactions.message
  253. -- @param user (string)
  254. -- @param message (string)
  255. function modpol.interactions.message(user, message)
  256. print(user .. ": " .. message)
  257. end
  258. --- Gets and sends a message from one user to another
  259. -- @function modpol.interactions.message_user
  260. -- @param sender Name of user sending (string)
  261. -- @param recipient Name of user receiving (string)
  262. function modpol.interactions.message_user(sender, recipient)
  263. print("Enter your message for "..recipient..":")
  264. local sel = io.read()
  265. modpol.interactions.message(
  266. recipient,
  267. sel.." [from "..sender.."]")
  268. end
  269. --- Displays complex data to a user
  270. -- @function modpol.interactions.display
  271. -- @param user Name of target user (string)
  272. -- @param title Title of display (string)
  273. -- @param message Content of message (string or table of strings)
  274. -- @param done Optional function for what happens when user is done
  275. function modpol.interactions.display(user, title, message, completion)
  276. local output = ""
  277. output = "\n-=< "..title.." >=-\n\n"
  278. if type(message) == "table" then
  279. output = table.concat(message,"\n")
  280. elseif type(message) == "string" then
  281. output = message
  282. elseif type(message) == "number" then
  283. output = message
  284. else
  285. modpol.interactions.message(
  286. self.initiator, "Error: message not typed for display")
  287. modpol.interactions.message(
  288. self.initiator, "Error: input not typed for display")
  289. if completion then completion() else
  290. modpol.intereactions.dashboard(user)
  291. end
  292. end
  293. print(message)
  294. print("\nEnter to continue")
  295. io.read()
  296. if completion then completion() else
  297. modpol.intereactions.dashboard(user)
  298. end
  299. end
  300. --- Applies "func" to user input.
  301. -- Func input: user input (string)
  302. -- @function modpol.interactions.text_query
  303. -- @param User (string)
  304. -- @param Query (string)
  305. -- @param func (function)
  306. function modpol.interactions.text_query(user, query, func)
  307. print(user .. ": " .. query)
  308. answer = io.read()
  309. func(answer)
  310. end
  311. --- Output: Calls func on choice.
  312. -- Func input: choice (string)
  313. -- @function modpol.interactions.dropdown_query
  314. -- @param user (string)
  315. -- @param label (string)
  316. -- @param options (table of strings)
  317. -- @param func (choice) (function)
  318. function modpol.interactions.dropdown_query(user, label, options, func)
  319. -- set up options
  320. local options_display = ""
  321. local options_number = 0
  322. for k,v in ipairs(options) do
  323. options_display = options_display .. k .. ". " ..
  324. options[k] .. "\n"
  325. options_number = options_number + 1
  326. end
  327. options_display = options_display .. "Select number:"
  328. if options_number == 0 then
  329. print("Error: No options given for dropdown")
  330. return nil
  331. end
  332. -- begin displaying
  333. print(user .. ": " .. label)
  334. print(options_display)
  335. -- read input and produce output
  336. local answer
  337. answer = io.read()
  338. answer = tonumber(answer)
  339. if answer then
  340. if answer >= 1 and answer <= options_number then
  341. print("Selection: " .. options[answer])
  342. func(options[answer])
  343. else
  344. print("Error: Not in dropdown range")
  345. return nil
  346. end
  347. else
  348. print("Error: Must be a number")
  349. return nil
  350. end
  351. end
  352. --- Allows user to select from a set of options
  353. -- @function modpol.interactions.checkbox_query
  354. -- @param user Name of user (string)
  355. -- @param label Query for user before options (string)
  356. -- @param options Table of options and their checked status in the form {{"option_1_string", true}, {"option_2_string", false}}
  357. -- @param func Function to be called with param "input", made up of the corrected table in the same format as the param options
  358. function modpol.interactions.checkbox_query(
  359. user, label, options, func)
  360. -- set up options
  361. local options_display = ""
  362. local options_number = 0
  363. for i,v in ipairs(options) do
  364. local checked = false
  365. if v[2] then checked = true end
  366. if checked then
  367. checked = "x"
  368. else
  369. checked = " "
  370. end
  371. options_display = options_display..i..". ["..
  372. checked.."] "..v[1].."\n"
  373. options_number = options_number + 1
  374. end
  375. if options_number == 0 then
  376. print("Error: No options given for dropdown")
  377. return nil
  378. end
  379. options_display = options_display..
  380. "List comma-separated options to flip (e.g., 1,2,5):"
  381. -- begin displaying
  382. print(user .. ": " .. label)
  383. print(options_display)
  384. -- read input and produce output
  385. local answer = io.read()
  386. local answer_table = {}
  387. for match in (answer..","):gmatch("(.-)"..",") do
  388. table.insert(answer_table, tonumber(match))
  389. end
  390. local result_table = modpol.util.copy_table(options)
  391. for i,v in ipairs(answer_table) do
  392. if result_table[v] then
  393. -- flip the boolean on selected options
  394. result_table[v][2] = not result_table[v][2]
  395. end
  396. end
  397. func(result_table)
  398. end
  399. --- Output: Applies "func" to user input.
  400. -- Func input: user input (string: y/n)
  401. -- @function modpol.interactions.binary_poll_user
  402. -- @param user (string)
  403. -- @param question (string)
  404. -- @param func (function)
  405. function modpol.interactions.binary_poll_user(user, question, func)
  406. local query = "Poll for " .. user .. " (y/n): ".. question
  407. local answer
  408. repeat
  409. print(query)
  410. answer = io.read()
  411. until answer == "y" or answer == "n"
  412. if answer == "y" then
  413. modpol.interactions.message(user, "Response recorded")
  414. func("Yes")
  415. elseif answer == "n" then
  416. modpol.interactions.message(user, "Response recorded")
  417. func("No")
  418. else
  419. modpol.interactions.message(user, "Error: invalid response")
  420. end
  421. end
  422. -- COMPLEX INTERACTIONS
  423. -- ====================
  424. --- Output: broadcasts message to all org members
  425. -- @function modpol.interactions.message_org
  426. -- @param initiator (string)
  427. -- @param org (number or string)
  428. -- @param message (string)
  429. function modpol.interactions.message_org(initiator, org, message)
  430. local this_org = modpol.orgs.get_org(org)
  431. local users = this_org:list_members()
  432. for k,v in ipairs(users) do
  433. modpol.interactions.message(v, message)
  434. end
  435. end
  436. -- Function: modpol.interactions.binary_poll_org
  437. -- input: initator (user string), org_id (number)
  438. -- output: gets question from initiator, asks all org members, broadcasts answers
  439. -- TESTING
  440. --testing command
  441. function modpol.msg(text)
  442. modpol.interactions.message("TEST MSG",text)
  443. end