interactions.lua 13 KB

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