+-- Generates a menu compatible with the first argument of `iterate` function and
+-- suitable for the following cases:
+-- * all possible choices individually (partition of singletons);
+-- * all possible subsets of the set of choices (powerset).
+--
+-- Inputs:
+-- * args: an array containing the following members:
+-- * choices: Array of choices (string) on which the menu will be
+-- generated.
+-- * name: Displayed name of the menu (in the form "name: choices").
+-- * selected_cb: Callback to execute for each selected choice. Takes
+-- the choice as a string argument. Can be `nil` (no action
+-- to execute).
+-- * rejected_cb: Callback to execute for each rejected choice (possible
+-- choices which are not selected). Takes the choice as a
+-- string argument. Can be `nil` (no action to execute).
+-- * extra_choices: An array of extra { choice_str, callback_fun } pairs to be
+-- added to the menu. Each callback_fun can be `nil`.
+-- * combination: The combination of choices to generate. Possible values:
+-- "powerset" and "single" (default).
+-- Output:
+-- * m: menu to be iterated over.
+local function menu(args)
+ local choices = assert(args.choices or args[1])
+ local name = assert(args.name or args[2])
+ local selected_cb = args.selected_cb
+ local rejected_cb = args.rejected_cb
+ local extra_choices = args.extra_choices or {}
+
+ local ch_combinations = args.combination == "powerset" and helpers.powerset(choices) or helpers.trivial_partition_set(choices)
+
+ for _,c in pairs(extra_choices) do
+ ch_combinations = atable.join(ch_combinations, {{c[1]}})
+ end
+
+ local m = {} -- the menu
+
+ for _,c in pairs(ch_combinations) do
+ if #c > 0 then
+ local cbs = {}
+
+ -- selected choices
+ for _,ch in pairs(c) do
+ if atable.hasitem(choices, ch) then
+ cbs[#cbs + 1] = selected_cb and function() selected_cb(ch) end or nil
+ end
+ end
+
+ -- rejected choices
+ for _,ch in pairs(choices) do
+ if not atable.hasitem(c, ch) and atable.hasitem(choices, ch) then
+ cbs[#cbs + 1] = rejected_cb and function() rejected_cb(ch) end or nil
+ end
+ end
+
+ -- add user extra choices (like the choice "None" for example)
+ for _,x in pairs(extra_choices) do
+ if x[1] == c[1] then
+ cbs[#cbs + 1] = x[2]
+ end
+ end
+
+ m[#m + 1] = { name .. ": " .. tconcat(c, " + "), cbs }
+ end
+ end
+
+ return m
+end
+
+return { iterate = iterate, menu = menu }