interactions.lua 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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. if v ~= "deleted" then
  152. local active = ''
  153. if org.pending[user] then
  154. if org.pending[user][v.id] then
  155. active = '*'
  156. end
  157. end
  158. print("["..v.id.."] "..v.slug..active)
  159. end
  160. end
  161. print()
  162. print("Interact with which one (use [id] number)?")
  163. local to_interact = io.read()
  164. local process = org.processes[tonumber(to_interact)]
  165. if not process then
  166. modpol.interactions.message(
  167. user, "Not a pending process")
  168. modpol.interactions.org_dashboard(user, org.id)
  169. return
  170. end
  171. if org:has_pending_actions(user) then
  172. if org.pending[user][process.id] then
  173. org:interact(process.id, user)
  174. end
  175. end
  176. elseif sel == 'b' or sel == 'B' then
  177. modpol.interactions.dashboard(user)
  178. else
  179. print("Command not found")
  180. modpol.interactions.org_dashboard(user, org.name)
  181. end
  182. end
  183. --- Function: modpol.interactions.user_dashboard
  184. -- Displays a dashboard about a particular user
  185. -- @param viewer Name of user viewing the dashboard (string)
  186. -- @param user Name of user being viewed (string)
  187. -- @param completion Optional function to call on Done button
  188. function modpol.interactions.user_dashboard(viewer, user, completion)
  189. local user_orgs = {}
  190. local user_modules = {}
  191. print("\n-=< USER DASHBOARD: "..user.." >=-")
  192. print("User's orgs:")
  193. for id, org in ipairs(modpol.orgs.array) do
  194. if type(org) == "table" then
  195. if org:has_member(user) then
  196. print(org.name)
  197. end
  198. end
  199. end
  200. print()
  201. print("Commands: (M)essage user, Enter when done")
  202. local sel = io.read()
  203. if sel == "M" or sel == "m" then
  204. modpol.interactions.message_user(
  205. viewer, user)
  206. completion()
  207. else
  208. completion()
  209. end
  210. end
  211. -- buttons: message, done
  212. -- INTERACTION PRIMITIVES
  213. -- ======================
  214. -- Function: modpol.interactions.message
  215. -- Produces a brief message to a user
  216. -- input: user (string), message (string)
  217. -- output: prints message to CLI
  218. function modpol.interactions.message(user, message)
  219. print(user .. ": " .. message)
  220. end
  221. --- Function: modpol.interactions.message_user
  222. -- Gets and sends a message from one user to another
  223. -- @param sender Name of user sending (string)
  224. -- @param recipient Name of user receiving (string)
  225. function modpol.interactions.message_user(sender, recipient)
  226. print("Enter your message for "..recipient..":")
  227. local sel = io.read()
  228. modpol.interactions.message(
  229. recipient,
  230. sel.." [from "..sender.."]")
  231. end
  232. --- Function: modpol.interactions.display
  233. -- Displays complex data to a user
  234. -- @param user Name of target user (string)
  235. -- @param title Title of display (string)
  236. -- @param message Content of message (string or table of strings)
  237. -- @param done Optional function for what happens when user is done
  238. function modpol.interactions.display(user, title, message, completion)
  239. local output = ""
  240. output = "\n-=< "..title.." >=-\n\n"
  241. if type(message) == "table" then
  242. output = table.concat(message,"\n")
  243. elseif type(message) == "string" then
  244. output = message
  245. elseif type(message) == "number" then
  246. output = message
  247. else
  248. modpol.interactions.message(
  249. self.initiator, "Error: message not typed for display")
  250. modpol.interactions.message(
  251. self.initiator, "Error: input not typed for display")
  252. if completion then completion() else
  253. modpol.intereactions.dashboard(user)
  254. end
  255. end
  256. print(message)
  257. print("\nEnter to continue")
  258. io.read()
  259. if completion then completion() else
  260. modpol.intereactions.dashboard(user)
  261. end
  262. end
  263. -- Function: modpol.interactions.text_query
  264. -- input: User (string), Query (string), func (function)
  265. -- func input: user input (string)
  266. -- output: Applies "func" to user input
  267. function modpol.interactions.text_query(user, query, func)
  268. print(user .. ": " .. query)
  269. answer = io.read()
  270. func(answer)
  271. end
  272. -- Function: dropdown_query
  273. -- input: user (string), label (string), options (table of strings), func(choice) (function)
  274. -- func input: choice (string)
  275. -- output: calls func on choice
  276. function modpol.interactions.dropdown_query(user, label, options, func)
  277. -- set up options
  278. local options_display = ""
  279. local options_number = 0
  280. for k,v in ipairs(options) do
  281. options_display = options_display .. k .. ". " ..
  282. options[k] .. "\n"
  283. options_number = options_number + 1
  284. end
  285. options_display = options_display .. "Select number:"
  286. if options_number == 0 then
  287. print("Error: No options given for dropdown")
  288. return nil
  289. end
  290. -- begin displaying
  291. print(user .. ": " .. label)
  292. print(options_display)
  293. -- read input and produce output
  294. local answer
  295. answer = io.read()
  296. answer = tonumber(answer)
  297. if answer then
  298. if answer >= 1 and answer <= options_number then
  299. print("Selection: " .. options[answer])
  300. func(options[answer])
  301. else
  302. print("Error: Not in dropdown range")
  303. return nil
  304. end
  305. else
  306. print("Error: Must be a number")
  307. return nil
  308. end
  309. end
  310. --- Function: modpol.interactions.checkbox_query
  311. -- Allows user to select from a set of options
  312. -- @param user Name of user (string)
  313. -- @param label Query for user before options (string)
  314. -- @param options table of options and their checked status in the form {{"option_1_string", true}, {"option_2_string", false}}
  315. -- @param func function to be called with param "input", made up of the corrected table in the same format as the param options
  316. function modpol.interactions.checkbox_query(
  317. user, label, options, func)
  318. -- set up options
  319. local options_display = ""
  320. local options_number = 0
  321. for i,v in ipairs(options) do
  322. local checked = false
  323. if v[2] then checked = true end
  324. if checked then
  325. checked = "x"
  326. else
  327. checked = " "
  328. end
  329. options_display = options_display..i..". ["..
  330. checked.."] "..v[1].."\n"
  331. options_number = options_number + 1
  332. end
  333. if options_number == 0 then
  334. print("Error: No options given for dropdown")
  335. return nil
  336. end
  337. options_display = options_display..
  338. "List comma-separated options to flip (e.g., 1,2,5):"
  339. -- begin displaying
  340. print(user .. ": " .. label)
  341. print(options_display)
  342. -- read input and produce output
  343. local answer = io.read()
  344. local answer_table = {}
  345. for match in (answer..","):gmatch("(.-)"..",") do
  346. table.insert(answer_table, tonumber(match))
  347. end
  348. local result_table = modpol.util.copy_table(options)
  349. for i,v in ipairs(answer_table) do
  350. if result_table[v] then
  351. -- flip the boolean on selected options
  352. result_table[v][2] = not result_table[v][2]
  353. end
  354. end
  355. func(result_table)
  356. end
  357. -- Function: modpol.interactions.binary_poll_user
  358. -- Params: user (string), question (string), func (function)
  359. -- func input: user input (string: y/n)
  360. -- Output: Applies "func" to user input
  361. function modpol.interactions.binary_poll_user(user, question, func)
  362. local query = "Poll for " .. user .. " (y/n): ".. question
  363. local answer
  364. repeat
  365. print(query)
  366. answer = io.read()
  367. until answer == "y" or answer == "n"
  368. if answer == "y" then
  369. modpol.interactions.message(user, "Response recorded")
  370. func("Yes")
  371. elseif answer == "n" then
  372. modpol.interactions.message(user, "Response recorded")
  373. func("No")
  374. else
  375. modpol.interactions.message(user, "Error: invalid response")
  376. end
  377. end
  378. -- COMPLEX INTERACTIONS
  379. -- ====================
  380. -- Function: modpol.interactions.message_org
  381. -- input: initiator (string), org (number or string), message (string)
  382. -- output: broadcasts message to all org members
  383. function modpol.interactions.message_org(initiator, org, message)
  384. local this_org = modpol.orgs.get_org(org)
  385. local users = this_org:list_members()
  386. for k,v in ipairs(users) do
  387. modpol.interactions.message(v, message)
  388. end
  389. end
  390. -- Function: modpol.interactions.binary_poll_org
  391. -- input: initator (user string), org_id (number)
  392. -- output: gets question from initiator, asks all org members, broadcasts answers
  393. -- TESTING
  394. --testing command
  395. function modpol.msg(text)
  396. modpol.interactions.message("TEST MSG",text)
  397. end