interactions.lua 13 KB

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