SquidDev

busted.api.lua

Dec 23rd, 2014
411
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 97.60 KB | None | 0 0
  1. local function _W(f) local e=setmetatable({}, {__index = getfenv()}) return setfenv(f,e)() or e end
  2. local say=_W(function()
  3. local registry = { }
  4. local current_namespace
  5. local fallback_namespace
  6.  
  7. local s = {
  8.  
  9.   _COPYRIGHT   = "Copyright (c) 2012 Olivine Labs, LLC.",
  10.   _DESCRIPTION = "A simple string key/value store for i18n or any other case where you want namespaced strings.",
  11.   _VERSION     = "Say 1.2",
  12.  
  13.   set_namespace = function(self, namespace)
  14.     current_namespace = namespace
  15.     if not registry[current_namespace] then
  16.       registry[current_namespace] = {}
  17.     end
  18.   end,
  19.  
  20.   set_fallback = function(self, namespace)
  21.     fallback_namespace = namespace
  22.     if not registry[fallback_namespace] then
  23.       registry[fallback_namespace] = {}
  24.     end
  25.   end,
  26.  
  27.   set = function(self, key, value)
  28.     registry[current_namespace][key] = value
  29.   end
  30. }
  31.  
  32. local __meta = {
  33.   __call = function(self, key, vars)
  34.     vars = vars or {}
  35.  
  36.     local str = registry[current_namespace][key] or registry[fallback_namespace][key]
  37.  
  38.     if str == nil then
  39.       return key
  40.     end
  41.     str = tostring(str)
  42.     local strings = {}
  43.  
  44.     for i,v in ipairs(vars) do
  45.       table.insert(strings, tostring(v))
  46.     end
  47.  
  48.     return #strings > 0 and str:format(unpack(strings)) or str
  49.   end,
  50.  
  51.   __index = function(self, key)
  52.     return registry[key]
  53.   end
  54. }
  55.  
  56. s:set_fallback('en')
  57. s:set_namespace('en')
  58.  
  59. if _TEST then
  60.   s._registry = registry -- force different name to make sure with _TEST behaves exactly as without _TEST
  61. end
  62.  
  63. return setmetatable(s, __meta)
  64. end)
  65. do
  66. local s = say
  67.  
  68. s:set_namespace('en')
  69.  
  70. -- 'Pending: test.lua @ 12 \n description
  71. s:set('output.pending', 'Pending')
  72. s:set('output.failure', 'Failure')
  73. s:set('output.error', 'Error')
  74. s:set('output.success', 'Success')
  75.  
  76. s:set('output.pending_plural', 'pending')
  77. s:set('output.failure_plural', 'failures')
  78. s:set('output.error_plural', 'errors')
  79. s:set('output.success_plural', 'successes')
  80.  
  81. s:set('output.pending_zero', 'pending')
  82. s:set('output.failure_zero', 'failures')
  83. s:set('output.error_zero', 'errors')
  84. s:set('output.success_zero', 'successes')
  85.  
  86. s:set('output.pending_single', 'pending')
  87. s:set('output.failure_single', 'failure')
  88. s:set('output.error_single', 'error')
  89. s:set('output.success_single', 'success')
  90.  
  91. s:set('output.seconds', 'seconds')
  92.  
  93. -- definitions following are not used within the 'say' namespace
  94. --[[
  95. return {
  96.   failure_messages = {
  97.     'You have %d busted specs',
  98.     'Your specs are busted',
  99.     'Your code is bad and you should feel bad',
  100.     'Your code is in the Danger Zone',
  101.     'Strange game. The only way to win is not to test',
  102.     'My grandmother wrote better specs on a 3 86',
  103.     'Every time there\'s a failure, drink another beer',
  104.     'Feels bad man'
  105.   },
  106.   success_messages = {
  107.     'Aww yeah, passing specs',
  108.     'Doesn\'t matter, had specs',
  109.     'Feels good, man',
  110.     'Great success',
  111.     'Tests pass, drink another beer',
  112.   }
  113. }
  114. ]]
  115. end
  116. local cliargs=_W(function()
  117. --[[
  118. Copyright (c) 2012 Ahmad Amireh
  119.  
  120. Permission is hereby granted, free of charge, to any person obtaining a copy of this
  121. software and associated documentation files (the "Software"), to deal in the Software
  122. without restriction, including without limitation the rights to use, copy, modify,
  123. merge, publish, distribute, sublicense, and/or sell copies of the Software,
  124. and to permit persons to whom the Software is furnished to do so,
  125. subject to the following conditions:
  126.  
  127. The above copyright notice and this permission notice shall be included in all copies
  128. or substantial portions of the Software.
  129.  
  130. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
  131. INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  132. PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  133. LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
  134.  TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  135.  OTHER DEALINGS IN THE SOFTWARE.
  136. ]]
  137.  
  138. local cli, _
  139.  
  140. -- ------- --
  141. -- Helpers --
  142. -- ------- --
  143.  
  144. local split = function(str, pat)
  145.   local t = {}
  146.   local fpat = "(.-)" .. pat
  147.   local last_end = 1
  148.   local s, e, cap = str:find(fpat, 1)
  149.   while s do
  150.     if s ~= 1 or cap ~= "" then
  151.       table.insert(t,cap)
  152.     end
  153.     last_end = e+1
  154.     s, e, cap = str:find(fpat, last_end)
  155.   end
  156.   if last_end <= #str then
  157.     cap = str:sub(last_end)
  158.     table.insert(t, cap)
  159.   end
  160.   return t
  161. end
  162.  
  163. local buildline = function(words, size, overflow)
  164.   -- if overflow is set, a word longer than size, will overflow the size
  165.   -- otherwise it will be chopped in line-length pieces
  166.   local line = ""
  167.   if string.len(words[1]) > size then
  168.     -- word longer than line
  169.     if overflow then
  170.       line = words[1]
  171.       table.remove(words, 1)
  172.     else
  173.       line = words[1]:sub(1, size)
  174.       words[1] = words[1]:sub(size + 1, -1)
  175.     end
  176.   else
  177.     while words[1] and (#line + string.len(words[1]) + 1 <= size) or (line == "" and #words[1] == size) do
  178.       if line == "" then
  179.         line = words[1]
  180.       else
  181.         line = line .. " " .. words[1]
  182.       end
  183.       table.remove(words, 1)
  184.     end
  185.   end
  186.   return line, words
  187. end
  188.  
  189. local wordwrap = function(str, size, pad, overflow)
  190.   -- if overflow is set, then words longer than a line will overflow
  191.   -- otherwise, they'll be chopped in pieces
  192.   pad = pad or 0
  193.  
  194.   local line = ""
  195.   local out = ""
  196.   local padstr = string.rep(" ", pad)
  197.   local words = split(str, ' ')
  198.  
  199.   while words[1] do
  200.     line, words = buildline(words, size, overflow)
  201.     if out == "" then
  202.       out = padstr .. line
  203.     else
  204.         out = out .. "\n" .. padstr .. line
  205.     end
  206.   end
  207.  
  208.   return out
  209. end
  210.  
  211. local function disect(key)
  212.   -- characters allowed are a-z, A-Z, 0-9
  213.   -- extended + values also allow; # @ _ + -
  214.   local k, ek, v
  215.   local dummy
  216.   -- if there is no comma, between short and extended, add one
  217.   _, _, dummy = key:find("^%-([%a%d])[%s]%-%-")
  218.   if dummy then key = key:gsub("^%-[%a%d][%s]%-%-", "-"..dummy..", --", 1) end
  219.   -- for a short key + value, replace space by "="
  220.   _, _, dummy = key:find("^%-([%a%d])[%s]")
  221.   if dummy then key = key:gsub("^%-([%a%d])[ ]", "-"..dummy.."=", 1) end
  222.   -- if there is no "=", then append one
  223.   if not key:find("=") then key = key .. "=" end
  224.   -- get value
  225.   _, _, v = key:find(".-%=(.+)")
  226.   -- get key(s), remove spaces
  227.   key = split(key, "=")[1]:gsub(" ", "")
  228.   -- get short key & extended key
  229.   _, _, k = key:find("^%-([%a%d]+)")
  230.   _, _, ek = key:find("%-%-(.+)$")
  231.   if v == "" then v = nil end
  232.   return k,ek,v
  233. end
  234.  
  235.  
  236. function cli_error(msg, noprint)
  237.   local msg = cli.name .. ": error: " .. msg .. '; re-run with --help for usage.'
  238.   if not noprint then print(msg) end
  239.   return nil, msg
  240. end
  241.  
  242. -- -------- --
  243. -- CLI Main --
  244. -- -------- --
  245.  
  246. cli = {
  247.   name = "",
  248.   required = {},
  249.   optional = {},
  250.   optargument = {maxcount = 0},
  251.   colsz = { 0, 0 }, -- column width, help text. Set to 0 for auto detect
  252.   maxlabel = 0,
  253. }
  254.  
  255. --- Assigns the name of the program which will be used for logging.
  256. function cli:set_name(name)
  257.   self.name = name
  258. end
  259.  
  260. -- Used internally to lookup an entry using either its short or expanded keys
  261. function cli:__lookup(k, ek, t)
  262.   t = t or self.optional
  263.   local _
  264.   for _,entry in ipairs(t) do
  265.     if k  and entry.key == k then return entry end
  266.     if ek and entry.expanded_key == ek then return entry end
  267.   end
  268.  
  269.   return nil
  270. end
  271.  
  272. --- Defines a required argument.
  273. --- Required arguments have no special notation and are order-sensitive.
  274. --- *Note:* the value will be stored in `args[@key]`.
  275. --- *Aliases: `add_argument`*
  276. ---
  277. --- ### Parameters
  278. --- 1. **key**: the argument's "name" that will be displayed to the user
  279. --- 1. **desc**: a description of the argument
  280. ---
  281. --- ### Usage example
  282. --- The following will parse the argument (if specified) and set its value in `args["root"]`:
  283. --- `cli:add_arg("root", "path to where root scripts can be found")`
  284. function cli:add_arg(key, desc)
  285.   assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
  286.  
  287.   if self:__lookup(key, nil, self.required) then
  288.     error("Duplicate argument: " .. key .. ", please rename one of them.")
  289.   end
  290.  
  291.   table.insert(self.required, { key = key, desc = desc, value = nil })
  292.   if #key > self.maxlabel then self.maxlabel = #key end
  293. end
  294.  
  295. --- Defines an optional argument (or more than one).
  296. --- There can be only 1 optional argument, and is has to be the last one on the argumentlist.
  297. --- *Note:* the value will be stored in `args[@key]`. The value will be a 'string' if 'maxcount == 1',
  298. --- or a table if 'maxcount > 1'
  299. ---
  300. --- ### Parameters
  301. --- 1. **key**: the argument's "name" that will be displayed to the user
  302. --- 1. **desc**: a description of the argument
  303. --- 1. **default**: *optional*; specify a default value (the default is "")
  304. --- 1. **maxcount**: *optional*; specify the maximum number of occurences allowed (default is 1)
  305. ---
  306. --- ### Usage example
  307. --- The following will parse the argument (if specified) and set its value in `args["root"]`:
  308. --- `cli:add_arg("root", "path to where root scripts can be found", "", 2)`
  309. --- The value returned will be a table with at least 1 entry and a maximum of 2 entries
  310. function cli:optarg(key, desc, default, maxcount)
  311.   assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
  312.   default = default or ""
  313.   assert(type(default) == "string", "Default value must either be omitted or be a string")
  314.   maxcount = maxcount or 1
  315.   maxcount = tonumber(maxcount)
  316.   assert(maxcount and maxcount>0 and maxcount<1000,"Maxcount must be a number from 1 to 999")
  317.  
  318.   self.optargument = { key = key, desc = desc, default = default, maxcount = maxcount, value = nil }
  319.   if #key > self.maxlabel then self.maxlabel = #key end
  320. end
  321.  
  322. --- Defines an option.
  323. --- Optional arguments can use 3 different notations, and can accept a value.
  324. --- *Aliases: `add_option`*
  325. ---
  326. --- ### Parameters
  327. --- 1. **key**: the argument identifier, can be either `-key`, or `-key, --expanded-key`:
  328. --- if the first notation is used then a value can be defined after a space (`'-key VALUE'`),
  329. --- if the 2nd notation is used then a value can be defined after an `=` (`'-key, --expanded-key=VALUE'`).
  330. --- As a final option it is possible to only use the expanded key (eg. `'--expanded-key'`) both with and
  331. --- without a value specified.
  332. --- 1. **desc**: a description for the argument to be shown in --help
  333. --- 1. **default**: *optional*; specify a default value (the default is "")
  334. ---
  335. --- ### Usage example
  336. --- The following option will be stored in `args["i"]` and `args["input"]` with a default value of `my_file.txt`:
  337. --- `cli:add_option("-i, --input=FILE", "path to the input file", "my_file.txt")`
  338. function cli:add_opt(key, desc, default)
  339.  
  340.   -- parameterize the key if needed, possible variations:
  341.   -- 1. -key
  342.   -- 2. -key VALUE
  343.   -- 3. -key, --expanded
  344.   -- 4. -key, --expanded=VALUE
  345.   -- 5. -key --expanded
  346.   -- 6. -key --expanded=VALUE
  347.   -- 7. --expanded
  348.   -- 8. --expanded=VALUE
  349.  
  350.   assert(type(key) == "string" and type(desc) == "string", "Key and description are mandatory arguments (Strings)")
  351.   assert(type(default) == "string" or default == nil or default == false, "Default argument: expected a string or nil")
  352.  
  353.   local k, ek, v = disect(key)
  354.  
  355.   if default == false and v ~= nil then
  356.     error("A flag type option cannot have a value set; " .. key)
  357.   end
  358.  
  359.   -- guard against duplicates
  360.   if self:__lookup(k, ek) then
  361.     error("Duplicate option: " .. (k or ek) .. ", please rename one of them.")
  362.   end
  363.  
  364.   -- set defaults
  365.   if v == nil then default = false end   -- no value, so its a flag
  366.   if default == nil then default = "" end
  367.  
  368.   -- below description of full entry record, nils included for reference
  369.   local entry = {
  370.     key = k,
  371.     expanded_key = ek,
  372.     desc = desc,
  373.     default = default,
  374.     label = key,
  375.     flag = (default == false),
  376.     value = default,
  377.   }
  378.  
  379.   table.insert(self.optional, entry)
  380.   if #key > self.maxlabel then self.maxlabel = #key end
  381.  
  382. end
  383.  
  384. --- Define a flag argument (on/off). This is a convenience helper for cli.add_opt().
  385. --- See cli.add_opt() for more information.
  386. ---
  387. --- ### Parameters
  388. -- 1. **key**: the argument's key
  389. -- 1. **desc**: a description of the argument to be displayed in the help listing
  390. function cli:add_flag(key, desc)
  391.   self:add_opt(key, desc, false)
  392. end
  393.  
  394. --- Parses the arguments found in #arg and returns a table with the populated values.
  395. --- (NOTE: after succesful parsing, the module will delete itself to free resources)
  396. --- *Aliases: `parse_args`*
  397. ---
  398. --- ### Parameters
  399. --- 1. **noprint**: set this flag to prevent any information (error or help info) from being printed
  400. --- 1. **dump**: set this flag to dump the parsed variables for debugging purposes, alternatively
  401. --- set the first option to --__DEBUG__ (option with 2 trailing and leading underscores) to dump at runtime.
  402. ---
  403. --- ### Returns
  404. --- 1. a table containing the keys specified when the arguments were defined along with the parsed values,
  405. --- or nil + error message (--help option is considered an error and returns nil + help message)
  406. function cli:parse(arg, noprint, dump)
  407.   arg = arg or _G.arg or {}
  408.   local args = {}
  409.   for k,v in pairs(arg) do args[k] = v end  -- copy global args local
  410.  
  411.   -- starts with --help? display the help listing and abort!
  412.   if args[1] and (args[1] == "--help" or args[1] == "-h") then
  413.     return nil, self:print_help(noprint)
  414.   end
  415.  
  416.   -- starts with --__DUMP__; set dump to true to dump the parsed arguments
  417.   if dump == nil then
  418.     if args[1] and args[1] == "--__DUMP__" then
  419.       dump = true
  420.       table.remove(args, 1)  -- delete it to prevent further parsing
  421.     end
  422.   end
  423.  
  424.   while args[1] do
  425.     local entry = nil
  426.     local opt = args[1]
  427.     local _, optpref, optkey, optkey2, optval
  428.     _, _, optpref, optkey = opt:find("^(%-[%-]?)(.+)")   -- split PREFIX & NAME+VALUE
  429.     if optkey then
  430.       _, _, optkey2, optval = optkey:find("(.-)[=](.+)")       -- split value and key
  431.       if optval then
  432.         optkey = optkey2
  433.       end
  434.     end
  435.  
  436.     if not optpref then
  437.       break   -- no optional prefix, so options are done
  438.     end
  439.  
  440.     if optkey:sub(-1,-1) == "=" then  -- check on a blank value eg. --insert=
  441.       optval = ""
  442.       optkey = optkey:sub(1,-2)
  443.     end
  444.  
  445.     if optkey then
  446.       entry =
  447.         self:__lookup(optpref == '-' and optkey or nil,
  448.                       optpref == '--' and optkey or nil)
  449.     end
  450.  
  451.     if not optkey or not entry then
  452.       local option_type = optval and "option" or "flag"
  453.       return cli_error("unknown/bad " .. option_type .. "; " .. opt, noprint)
  454.     end
  455.  
  456.     table.remove(args,1)
  457.     if optpref == "-" then
  458.       if optval then
  459.         return cli_error("short option does not allow value through '='; "..opt, noprint)
  460.       end
  461.       if entry.flag then
  462.         optval = true
  463.       else
  464.         -- not a flag, value is in the next argument
  465.         optval = args[1]
  466.         table.remove(args, 1)
  467.       end
  468.     elseif optpref == "--" then
  469.       -- using the expanded-key notation
  470.       entry = self:__lookup(nil, optkey)
  471.       if entry then
  472.         if entry.flag then
  473.           if optval then
  474.             return cli_error("flag --" .. optkey .. " does not take a value", noprint)
  475.           else
  476.             optval = true
  477.           end
  478.         else
  479.           if not optval then
  480.             return cli_error("option --" .. optkey .. " requires a value to be set", noprint)
  481.           end
  482.         end
  483.       else
  484.         return cli_error("unknown/bad flag; " .. opt, noprint)
  485.       end
  486.     end
  487.  
  488.     entry.value = optval
  489.   end
  490.  
  491.   -- missing any required arguments, or too many?
  492.   if #args < #self.required or #args > #self.required + self.optargument.maxcount then
  493.     if self.optargument.maxcount > 0 then
  494.       return cli_error("bad number of arguments; " .. #self.required .."-" .. #self.required + self.optargument.maxcount .. " argument(s) must be specified, not " .. #args, noprint)
  495.     else
  496.       return cli_error("bad number of arguments; " .. #self.required .. " argument(s) must be specified, not " .. #args, noprint)
  497.     end
  498.   end
  499.  
  500.   -- deal with required args here
  501.   for i, entry in ipairs(self.required) do
  502.     entry.value = args[1]
  503.     table.remove(args, 1)
  504.   end
  505.   -- deal with the last optional argument
  506.   while args[1] do
  507.     if self.optargument.maxcount > 1 then
  508.       self.optargument.value = self.optargument.value or {}
  509.       table.insert(self.optargument.value, args[1])
  510.     else
  511.       self.optargument.value = args[1]
  512.     end
  513.     table.remove(args,1)
  514.   end
  515.   -- if necessary set the defaults for the last optional argument here
  516.   if self.optargument.maxcount > 0 and not self.optargument.value then
  517.     if self.optargument.maxcount == 1 then
  518.       self.optargument.value = self.optargument.default
  519.     else
  520.       self.optargument.value = { self.optargument.default }
  521.     end
  522.   end
  523.  
  524.   -- populate the results table
  525.   local results = {}
  526.   if self.optargument.maxcount > 0 then
  527.     results[self.optargument.key] = self.optargument.value
  528.   end
  529.   for _, entry in pairs(self.required) do
  530.     results[entry.key] = entry.value
  531.   end
  532.   for _, entry in pairs(self.optional) do
  533.     if entry.key then results[entry.key] = entry.value end
  534.     if entry.expanded_key then results[entry.expanded_key] = entry.value end
  535.   end
  536.  
  537.   if dump then
  538.     print("\n======= Provided command line =============")
  539.     print("\nNumber of arguments: ", #arg)
  540.     for i,v in ipairs(arg) do -- use gloabl 'arg' not the modified local 'args'
  541.       print(string.format("%3i = '%s'", i, v))
  542.     end
  543.  
  544.     print("\n======= Parsed command line ===============")
  545.     if #self.required > 0 then print("\nArguments:") end
  546.     for i,v in ipairs(self.required) do
  547.       print("  " .. v.key .. string.rep(" ", self.maxlabel + 2 - #v.key) .. " => '" .. v.value .. "'")
  548.     end
  549.  
  550.     if self.optargument.maxcount > 0 then
  551.       print("\nOptional arguments:")
  552.       print("  " .. self.optargument.key .. "; allowed are " .. tostring(self.optargument.maxcount) .. " arguments")
  553.       if self.optargument.maxcount == 1 then
  554.           print("  " .. self.optargument.key .. string.rep(" ", self.maxlabel + 2 - #self.optargument.key) .. " => '" .. self.optargument.key .. "'")
  555.       else
  556.         for i = 1, self.optargument.maxcount do
  557.           if self.optargument.value[i] then
  558.             print("  " .. tostring(i) .. string.rep(" ", self.maxlabel + 2 - #tostring(i)) .. " => '" .. tostring(self.optargument.value[i]) .. "'")
  559.           end
  560.         end
  561.       end
  562.     end
  563.  
  564.     if #self.optional > 0 then print("\nOptional parameters:") end
  565.     local doubles = {}
  566.     for _, v in pairs(self.optional) do
  567.       if not doubles[v] then
  568.         local m = v.value
  569.         if type(m) == "string" then
  570.           m = "'"..m.."'"
  571.         else
  572.           m = tostring(m) .." (" .. type(m) .. ")"
  573.         end
  574.         print("  " .. v.label .. string.rep(" ", self.maxlabel + 2 - #v.label) .. " => " .. m)
  575.         doubles[v] = v
  576.       end
  577.     end
  578.     print("\n===========================================\n\n")
  579.     return cli_error("commandline dump created as requested per '--__DUMP__' option", noprint)
  580.   end
  581.  
  582.   return results
  583. end
  584.  
  585. --- Prints the USAGE heading.
  586. ---
  587. --- ### Parameters
  588.  ---1. **noprint**: set this flag to prevent the line from being printed
  589. ---
  590. --- ### Returns
  591. --- 1. a string with the USAGE message.
  592. function cli:print_usage(noprint)
  593.   -- print the USAGE heading
  594.   local msg = "Usage: " .. tostring(self.name)
  595.   if self.optional[1] then
  596.     msg = msg .. " [OPTIONS] "
  597.   end
  598.   if self.required[1] then
  599.     for _,entry in ipairs(self.required) do
  600.       msg = msg .. " " .. entry.key .. " "
  601.     end
  602.   end
  603.   if self.optargument.maxcount == 1 then
  604.     msg = msg .. " [" .. self.optargument.key .. "]"
  605.   elseif self.optargument.maxcount == 2 then
  606.     msg = msg .. " [" .. self.optargument.key .. "-1 [" .. self.optargument.key .. "-2]]"
  607.   elseif self.optargument.maxcount > 2 then
  608.     msg = msg .. " [" .. self.optargument.key .. "-1 [" .. self.optargument.key .. "-2 [...]]]"
  609.   end
  610.  
  611.   if not noprint then print(msg) end
  612.   return msg
  613. end
  614.  
  615.  
  616. --- Prints the HELP information.
  617. ---
  618. --- ### Parameters
  619. --- 1. **noprint**: set this flag to prevent the information from being printed
  620. ---
  621. --- ### Returns
  622. --- 1. a string with the HELP message.
  623. function cli:print_help(noprint)
  624.  
  625.   local msg = self:print_usage(true) .. "\n"
  626.   local col1 = self.colsz[1]
  627.   local col2 = self.colsz[2]
  628.   if col1 == 0 then col1 = cli.maxlabel end
  629.   col1 = col1 + 3     --add margins
  630.   if col2 == 0 then col2 = 72 - col1 end
  631.   if col2 <10 then col2 = 10 end
  632.  
  633.   local append = function(label, desc)
  634.       label = "  " .. label .. string.rep(" ", col1 - (#label + 2))
  635.       desc = wordwrap(desc, col2)   -- word-wrap
  636.       desc = desc:gsub("\n", "\n" .. string.rep(" ", col1)) -- add padding
  637.  
  638.       msg = msg .. label .. desc .. "\n"
  639.   end
  640.  
  641.   if self.required[1] then
  642.     msg = msg .. "\nARGUMENTS: \n"
  643.     for _,entry in ipairs(self.required) do
  644.       append(entry.key, entry.desc .. " (required)")
  645.     end
  646.   end
  647.  
  648.   if self.optargument.maxcount >0 then
  649.     append(self.optargument.key, self.optargument.desc .. " (optional, default: " .. self.optargument.default .. ")")
  650.   end
  651.  
  652.   if self.optional[1] then
  653.     msg = msg .. "\nOPTIONS: \n"
  654.  
  655.     for _,entry in ipairs(self.optional) do
  656.       local desc = entry.desc
  657.       if not entry.flag and entry.default and #tostring(entry.default) > 0 then
  658.         desc = desc .. " (default: " .. entry.default .. ")"
  659.       end
  660.       append(entry.label, desc)
  661.     end
  662.   end
  663.  
  664.   if not noprint then print(msg) end
  665.   return msg
  666. end
  667.  
  668. --- Sets the amount of space allocated to the argument keys and descriptions in the help listing.
  669. --- The sizes are used for wrapping long argument keys and descriptions.
  670. --- ### Parameters
  671. --- 1. **key_cols**: the number of columns assigned to the argument keys, set to 0 to auto detect (default: 0)
  672. --- 1. **desc_cols**: the number of columns assigned to the argument descriptions, set to 0 to auto set the total width to 72 (default: 0)
  673. function cli:set_colsz(key_cols, desc_cols)
  674.   self.colsz = { key_cols or self.colsz[1], desc_cols or self.colsz[2] }
  675. end
  676.  
  677.  
  678. -- finalize setup
  679. cli._COPYRIGHT   = "Copyright (C) 2011-2012 Ahmad Amireh"
  680. cli._LICENSE     = "The code is released under the MIT terms. Feel free to use it in both open and closed software as you please."
  681. cli._DESCRIPTION = "Commandline argument parser for Lua"
  682. cli._VERSION     = "cliargs 2.0-1"
  683.  
  684. -- aliases
  685. cli.add_argument = cli.add_arg
  686. cli.add_option = cli.add_opt
  687. cli.parse_args = cli.parse    -- backward compatibility
  688.  
  689. -- test aliases for local functions
  690. if _TEST then
  691.   cli.split = split
  692.   cli.wordwrap = wordwrap
  693. end
  694.  
  695. return cli
  696. end)
  697. local outputHandlerBase=_W(function()
  698. return function(busted)
  699.   local handler = {
  700.     successes = {},
  701.     successesCount = 0,
  702.     pendings = {},
  703.     pendingsCount = 0,
  704.     failures = {},
  705.     failuresCount = 0,
  706.     errors = {},
  707.     errorsCount = 0,
  708.     inProgress = {}
  709.   }
  710.  
  711.   handler.cancelOnPending = function(element, parent, status)
  712.     return not ((element.descriptor == 'pending' or status == 'pending') and handler.options.suppressPending)
  713.   end
  714.  
  715.   handler.subscribe = function(handler, options)
  716.     handler.options = options
  717.  
  718.     busted.subscribe({ 'suite', 'start' }, handler.baseSuiteStart)
  719.     busted.subscribe({ 'suite', 'end' }, handler.baseSuiteEnd)
  720.     busted.subscribe({ 'test', 'start' }, handler.baseTestStart, { predicate = handler.cancelOnPending })
  721.     busted.subscribe({ 'test', 'end' }, handler.baseTestEnd, { predicate = handler.cancelOnPending })
  722.     busted.subscribe({ 'pending' }, handler.basePending, { predicate = handler.cancelOnPending })
  723.     busted.subscribe({ 'failure' }, handler.baseError)
  724.     busted.subscribe({ 'error' }, handler.baseError)
  725.   end
  726.  
  727.   handler.getFullName = function(context)
  728.     local parent = busted.context.parent(context)
  729.     local names = { (context.name or context.descriptor) }
  730.  
  731.     while parent and (parent.name or parent.descriptor) and
  732.           parent.descriptor ~= 'file' do
  733.  
  734.       table.insert(names, 1, parent.name or parent.descriptor)
  735.       parent = busted.context.parent(parent)
  736.     end
  737.  
  738.     return table.concat(names, ' ')
  739.   end
  740.  
  741.   handler.format = function(element, parent, message, debug, isError)
  742.     local formatted = {
  743.       trace = debug or element.trace,
  744.       element = element,
  745.       name = handler.getFullName(element),
  746.       message = message,
  747.       isError = isError
  748.     }
  749.     formatted.element.trace = element.trace or debug
  750.  
  751.     return formatted
  752.   end
  753.  
  754.   handler.getDuration = function()
  755.     if not handler.endTime or not handler.startTime then
  756.       return 0
  757.     end
  758.  
  759.     return handler.endTime - handler.startTime
  760.   end
  761.  
  762.   handler.baseSuiteStart = function(name, parent)
  763.     handler.startTime = os.clock()
  764.  
  765.     return nil, true
  766.   end
  767.  
  768.   handler.baseSuiteEnd = function(name, parent)
  769.     handler.endTime = os.clock()
  770.     return nil, true
  771.   end
  772.  
  773.   handler.baseTestStart = function(element, parent)
  774.     handler.inProgress[tostring(element)] = {}
  775.     return nil, true
  776.   end
  777.  
  778.   handler.baseTestEnd = function(element, parent, status, debug)
  779.  
  780.     local isError
  781.     local insertTable
  782.     local id = tostring(element)
  783.  
  784.     if status == 'success' then
  785.       insertTable = handler.successes
  786.       handler.successesCount = handler.successesCount + 1
  787.     elseif status == 'pending' then
  788.       insertTable = handler.pendings
  789.       handler.pendingsCount = handler.pendingsCount + 1
  790.     elseif status == 'failure' then
  791.       insertTable = handler.failures
  792.       handler.failuresCount = handler.failuresCount + 1
  793.     elseif status == 'error' then
  794.       insertTable = handler.errors
  795.       handler.errorsCount = handler.errorsCount + 1
  796.       isError = true
  797.     end
  798.  
  799.     insertTable[id] = handler.format(element, parent, element.message, debug, isError)
  800.  
  801.     if handler.inProgress[id] then
  802.       for k, v in pairs(handler.inProgress[id]) do
  803.         insertTable[id][k] = v
  804.       end
  805.  
  806.       handler.inProgress[id] = nil
  807.     end
  808.  
  809.     return nil, true
  810.   end
  811.  
  812.   handler.basePending = function(element, parent, message, debug)
  813.     if element.descriptor == 'it' then
  814.       local id = tostring(element)
  815.       handler.inProgress[id].message = message
  816.       handler.inProgress[id].trace = debug
  817.     end
  818.  
  819.     return nil, true
  820.   end
  821.  
  822.   handler.baseError = function(element, parent, message, debug)
  823.     if element.descriptor == 'it' then
  824.       if parent.randomseed then
  825.         message = 'Random Seed: ' .. parent.randomseed .. '\n' .. message
  826.       end
  827.       local id = tostring(element)
  828.       handler.inProgress[id].message = message
  829.       handler.inProgress[id].trace = debug
  830.     else
  831.       handler.errorsCount = handler.errorsCount + 1
  832.       table.insert(handler.errors, handler.format(element, parent, message, debug, true))
  833.     end
  834.  
  835.     return nil, true
  836.   end
  837.  
  838.   return handler
  839. end
  840. end)
  841. local ansicolors=_W(function()
  842. --- Simple module for handling colours
  843. -- use like colors("%{red}HELLO %{green bluebg}World %{reset}Awesome?"):print()
  844.  
  845. local isColor,setBack,setFore = term.isColor,term.setBackgroundColor,term.setTextColor
  846. local back,fore=colors.black,colors.white
  847. local insert=table.insert
  848.  
  849. local function colorGenerate(color, background)
  850.     local func = background and setBack or setFore
  851.     return function()
  852.         if isColor() then func(color) end
  853.     end
  854. end
  855.  
  856. local escape = {
  857.     reset = function()
  858.         setBack(back)
  859.         setFore(fore)
  860.     end,
  861. }
  862.  
  863. -- Load in keys
  864. for name, color in pairs(colors) do
  865.     if type(color) == "number" then
  866.         escape[name] = colorGenerate(color)
  867.         escape[name .. "bg"] = colorGenerate(color, true)
  868.     end
  869. end
  870.  
  871. for name, color in pairs(colours) do
  872.     if type(color) == "number" and not escape[name] then
  873.         escape[name] = colorGenerate(color)
  874.         escape[name .. "bg"] = colorGenerate(color, true)
  875.     end
  876. end
  877.  
  878. -- Define a basic object
  879. local ansiObjectMethods = {}
  880. local ansiObject = {__index = ansiObjectMethods}
  881.  
  882. function ansiObject:__concat(val)
  883.     local buffer = setmetatable({}, ansiObject)
  884.  
  885.     if type(self) == "table" then
  886.         for _, value in ipairs(self) do
  887.             insert(buffer, value)
  888.         end
  889.     else
  890.         insert(buffer, tostring(self))
  891.     end
  892.  
  893.     if type(val) == "table" then
  894.         for _, value in ipairs(val) do
  895.             insert(buffer, value)
  896.         end
  897.     else
  898.         insert(buffer, tostring(val))
  899.     end
  900.  
  901.     return buffer
  902. end
  903.  
  904. function ansiObject:__tostring(val)
  905.     local type=type
  906.     local buffer = {}
  907.     for _, v in ipairs(self) do
  908.         if type(v) ~= "function" then
  909.             insert(buffer, v)
  910.         end
  911.     end
  912.  
  913.     return table.concat(buffer)
  914. end
  915.  
  916. function ansiObjectMethods:write()
  917.     local reset = escape.reset
  918.     reset()
  919.  
  920.     local type,write = type,write
  921.     for _, v in ipairs(self) do
  922.         if type(v) == "function" then
  923.             v()
  924.         else
  925.             write(v)
  926.         end
  927.     end
  928.     reset()
  929. end
  930.  
  931. ansiObject.__call = ansiObjectMethods.print
  932.  
  933. function ansiObjectMethods:print()
  934.     self:write()
  935.     print()
  936. end
  937.  
  938. return function(str)
  939.     str = tostring(str or '')
  940.  
  941.     local buffer = setmetatable({}, ansiObject)
  942.     local pos,len = 1,#str
  943.  
  944.     if len == 0 then return buffer end
  945.     while pos <= len do
  946.         local first,last,keys = str:find("%%{(.-)}", pos)
  947.         if first == nil then
  948.             insert(buffer, str:sub(pos))
  949.             break
  950.         end
  951.  
  952.         local gap = str:sub(pos, first - 1)
  953.         if #gap > 0 then
  954.             insert(buffer, gap)
  955.         end
  956.  
  957.         for key in keys:gmatch("%w+") do
  958.             insert(buffer, (assert(escape[key], "Cannot find key " .. (key or "<nil>"))))
  959.         end
  960.         pos = last + 1
  961.     end
  962.  
  963.     return buffer
  964. end
  965. end)
  966. local colorTerminal=_W(function()
  967. local s = say
  968.  
  969. return function(options, busted)
  970.   local handler = outputHandlerBase(busted)
  971.   local successDot =  ansicolors('%{green}+')
  972.   local failureDot =  ansicolors('%{red}-')
  973.   local errorDot =  ansicolors('%{magenta}*')
  974.   local pendingDot = ansicolors('%{yellow}.')
  975.  
  976.   local pendingDescription = function(pending)
  977.     local name = pending.name
  978.  
  979.     local string = ansicolors('%{yellow}' .. s('output.pending')) .. ' => ' ..
  980.       ansicolors('%{cyan}' .. pending.trace.short_src) .. ' @ ' ..
  981.       ansicolors('%{cyan}' .. pending.trace.currentline)  ..
  982.       ansicolors('%{white}\n' .. name)
  983.  
  984.     if type(pending.message) == 'string' then
  985.       string = string .. '\n' .. pending.message
  986.     elseif pending.message ~= nil then
  987.       string = string .. '\n' .. textutils.serialize(pending.message)
  988.     end
  989.  
  990.     return string
  991.   end
  992.  
  993.   local failureMessage = function(failure)
  994.     local string
  995.     if type(failure.message) == 'string' then
  996.       string = failure.message
  997.     elseif failure.message == nil then
  998.       string = 'Nil error'
  999.     else
  1000.       string = textutils.serialize(failure.message)
  1001.     end
  1002.  
  1003.     return string
  1004.   end
  1005.  
  1006.   local failureDescription = function(failure, isError)
  1007.     local string = '%{red}' .. s('output.failure') .. ' => '
  1008.     if isError then
  1009.       string = '%{magenta}' .. s('output.error') .. ' => '
  1010.     end
  1011.  
  1012.     if not failure.element.trace or not failure.element.trace.short_src then
  1013.       string = string ..
  1014.         '%{cyan}' .. failureMessage(failure) .. '\n' .. '%{white}' .. failure.name
  1015.     else
  1016.       string = string ..
  1017.         '%{cyan}' .. failure.element.trace.short_src .. ' @ ' ..
  1018.         '%{cyan}' .. failure.element.trace.currentline .. '\n' ..
  1019.         '%{white}' .. failure.name .. '\n%{lightGray}' .. failureMessage(failure)
  1020.     end
  1021.  
  1022.     if options.verbose and failure.trace and failure.trace.traceback then
  1023.       string = string .. '\n%{gray}' .. failure.trace.traceback
  1024.     end
  1025.  
  1026.     return ansicolors(string)
  1027.   end
  1028.  
  1029.   local statusString = function()
  1030.     local successString = s('output.success_plural')
  1031.     local failureString = s('output.failure_plural')
  1032.     local pendingString = s('output.pending_plural')
  1033.     local errorString = s('output.error_plural')
  1034.  
  1035.     local ms = handler.getDuration()
  1036.     local successes = handler.successesCount
  1037.     local pendings = handler.pendingsCount
  1038.     local failures = handler.failuresCount
  1039.     local errors = handler.errorsCount
  1040.  
  1041.     if successes == 0 then
  1042.       successString = s('output.success_zero')
  1043.     elseif successes == 1 then
  1044.       successString = s('output.success_single')
  1045.     end
  1046.  
  1047.     if failures == 0 then
  1048.       failureString = s('output.failure_zero')
  1049.     elseif failures == 1 then
  1050.       failureString = s('output.failure_single')
  1051.     end
  1052.  
  1053.     if pendings == 0 then
  1054.       pendingString = s('output.pending_zero')
  1055.     elseif pendings == 1 then
  1056.       pendingString = s('output.pending_single')
  1057.     end
  1058.  
  1059.     if errors == 0 then
  1060.       errorString = s('output.error_zero')
  1061.     elseif errors == 1 then
  1062.       errorString = s('output.error_single')
  1063.     end
  1064.  
  1065.     local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1')
  1066.     return ansicolors('%{green}' .. successes) .. ' ' .. successString .. ' / ' ..
  1067.       ansicolors('%{red}' .. failures) .. ' ' .. failureString .. ' / ' ..
  1068.       ansicolors('%{magenta}' .. errors) .. ' ' .. errorString .. ' / ' ..
  1069.       ansicolors('%{yellow}' .. pendings) .. ' ' .. pendingString .. ' : ' ..
  1070.       ansicolors('%{white}' .. formattedTime) .. ' ' .. s('output.seconds')
  1071.   end
  1072.  
  1073.   handler.testEnd = function(element, parent, status, debug)
  1074.     if not options.deferPrint then
  1075.       local string = successDot
  1076.  
  1077.       if status == 'pending' then
  1078.         string = pendingDot
  1079.       elseif status == 'failure' then
  1080.         string = failureDot
  1081.       elseif status == 'error' then
  1082.         string = errorDot
  1083.       end
  1084.  
  1085.       string:write()
  1086.     end
  1087.  
  1088.     return nil, true
  1089.   end
  1090.  
  1091.   handler.suiteEnd = function(name, parent)
  1092.     print('')
  1093.     statusString():print()
  1094.  
  1095.     for i, pending in pairs(handler.pendings) do
  1096.       print('')
  1097.       pendingDescription(pending):print()
  1098.     end
  1099.  
  1100.     for i, err in pairs(handler.failures) do
  1101.       print('')
  1102.       failureDescription(err):print()
  1103.     end
  1104.  
  1105.     for i, err in pairs(handler.errors) do
  1106.       print('')
  1107.       failureDescription(err, true):print()
  1108.     end
  1109.  
  1110.     return nil, true
  1111.   end
  1112.  
  1113.   handler.error = function(element, parent, message, debug)
  1114.     errorDot:write()
  1115.  
  1116.     return nil, true
  1117.   end
  1118.  
  1119.   busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
  1120.   busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
  1121.   busted.subscribe({ 'error', 'file' }, handler.error)
  1122.   busted.subscribe({ 'error', 'describe' }, handler.error)
  1123.  
  1124.   return handler
  1125. end
  1126. end)
  1127. local state=_W(function()
  1128. -- maintains a state of the assert engine in a linked-list fashion
  1129. -- records; formatters, parameters, spies and stubs
  1130.  
  1131. local state_mt = {
  1132.       __call = function(self)
  1133.         self:revert()
  1134.       end }
  1135.  
  1136. local nilvalue = {} -- unique ID to refer to nil values for parameters
  1137.  
  1138. -- will hold the current state
  1139. local current
  1140.  
  1141. -- exported module table
  1142. local state = {}
  1143.  
  1144. ------------------------------------------------------
  1145. -- Reverts to a (specific) snapshot.
  1146. -- @param self (optional) the snapshot to revert to. If not provided, it will revert to the last snapshot.
  1147. state.revert = function(self)
  1148.   if not self then
  1149.     -- no snapshot given, so move 1 up
  1150.     self = current
  1151.     if not self.previous then
  1152.       -- top of list, no previous one, nothing to do
  1153.       return
  1154.     end
  1155.   end
  1156.   if getmetatable(self) ~= state_mt then error("Value provided is not a valid snapshot", 2) end
  1157.  
  1158.   if self.next then
  1159.     self.next:revert()
  1160.   end
  1161.   -- revert formatters in 'last'
  1162.   self.formatters = {}
  1163.   -- revert parameters in 'last'
  1164.   self.parameters = {}
  1165.   -- revert spies/stubs in 'last'
  1166.   while self.spies[1] do
  1167.     self.spies[1]:revert()
  1168.     table.remove(self.spies, 1)
  1169.   end
  1170.   setmetatable(self, nil) -- invalidate as a snapshot
  1171.   current = self.previous
  1172.   current.next = nil
  1173. end
  1174.  
  1175. ------------------------------------------------------
  1176. -- Creates a new snapshot.
  1177. -- @return snapshot table
  1178. state.snapshot = function()
  1179.   local s = current
  1180.   local new = setmetatable ({
  1181.     formatters = {},
  1182.     parameters = {},
  1183.     spies = {},
  1184.     previous = current,
  1185.     revert = state.revert,
  1186.   }, state_mt)
  1187.   if current then current.next = new end
  1188.   current = new
  1189.   return current
  1190. end
  1191.  
  1192.  
  1193. --  FORMATTERS
  1194. state.add_formatter = function(callback)
  1195.   table.insert(current.formatters, 1, callback)
  1196. end
  1197.  
  1198. state.remove_formatter = function(callback, s)
  1199.   s = s or current
  1200.   for i, v in ipairs(s.formatters) do
  1201.     if v == callback then
  1202.       table.remove(s.formatters, i)
  1203.       break
  1204.     end
  1205.   end
  1206.   -- wasn't found, so traverse up 1 state
  1207.   if s.previous then
  1208.     state.remove_formatter(callback, s.previous)
  1209.   end
  1210. end
  1211.  
  1212. state.format_argument = function(val, s)
  1213.   s = s or current
  1214.   for _, fmt in ipairs(s.formatters) do
  1215.     local valfmt = fmt(val)
  1216.     if valfmt ~= nil then return valfmt end
  1217.   end
  1218.   -- nothing found, check snapshot 1 up in list
  1219.   if s.previous then
  1220.     return state.format_argument(val, s.previous)
  1221.   end
  1222.   return nil -- end of list, couldn't format
  1223. end
  1224.  
  1225.  
  1226. --  PARAMETERS
  1227. state.set_parameter = function(name, value)
  1228.   if value == nil then value = nilvalue end
  1229.   current.parameters[name] = value
  1230. end
  1231.  
  1232. state.get_parameter = function(name, s)
  1233.   s = s or current
  1234.   local val = s.parameters[name]
  1235.   if val == nil and s.previous then
  1236.     -- not found, so check 1 up in list
  1237.     return state.get_parameter(name, s.previous)
  1238.   end
  1239.   if val ~= nilvalue then
  1240.     return val
  1241.   end
  1242.   return nil
  1243. end
  1244.  
  1245. --  SPIES / STUBS
  1246. state.add_spy = function(spy)
  1247.   table.insert(current.spies, 1, spy)
  1248. end
  1249.  
  1250. state.snapshot()  -- create initial state
  1251.  
  1252. return state
  1253. end)
  1254. local assert=_W(function()
  1255. local s = say
  1256. local astate = state
  1257. local obj   -- the returned module table
  1258.  
  1259. -- list of namespaces
  1260. local namespace = {}
  1261.  
  1262. local errorlevel = function()
  1263.   -- find the first level, not defined in the same file as this
  1264.   -- code file to properly report the error
  1265.   local level = 1
  1266.   local _, info = pcall(error, "", level+1)
  1267.   local thisfile = info:match("[^:]+")
  1268.   while thisfile and info:find(thisfile, 1, plain) do
  1269.     level = level + 1
  1270.     _, info = pcall(error, "", level)
  1271.   end
  1272.   if level > 1 then level = level - 1 end -- deduct call to errorlevel() itself
  1273.   return level
  1274. end
  1275.  
  1276. local function extract_keys(assert_string)
  1277.   -- get a list of token separated by _
  1278.   local tokens = {}
  1279.   for token in assert_string:lower():gmatch('[^_]+') do
  1280.     table.insert(tokens, token)
  1281.   end
  1282.  
  1283.   -- find valid keys by coalescing tokens as needed, starting from the end
  1284.   local keys = {}
  1285.   local key = nil
  1286.   for i = #tokens, 1, -1 do
  1287.     local token = tokens[i]
  1288.     key = key and (token .. '_' .. key) or token
  1289.     if namespace.modifier[key] or namespace.assertion[key] then
  1290.       table.insert(keys, 1, key)
  1291.       key = nil
  1292.     end
  1293.   end
  1294.  
  1295.   -- if there's anything left we didn't recognize it
  1296.   if key then
  1297.     error("luassert: unknown modifier/assertion: '" .. key .."'", errorlevel())
  1298.   end
  1299.  
  1300.   return keys
  1301. end
  1302.  
  1303. local __assertion_meta = {
  1304.   __call = function(self, ...)
  1305.     local state = self.state
  1306.     local arguments = {...}
  1307.     arguments.n = select('#',...)  -- add argument count for trailing nils
  1308.     local val = self.callback(state, arguments)
  1309.     local data_type = type(val)
  1310.  
  1311.     if data_type == "boolean" then
  1312.       if val ~= state.mod then
  1313.         if state.mod then
  1314.           error(s(self.positive_message, obj:format(arguments)) or "assertion failed!", errorlevel())
  1315.         else
  1316.           error(s(self.negative_message, obj:format(arguments)) or "assertion failed!", errorlevel())
  1317.         end
  1318.       else
  1319.         return state
  1320.       end
  1321.     end
  1322.     return val
  1323.   end
  1324. }
  1325.  
  1326. local __state_meta = {
  1327.  
  1328.   __call = function(self, payload, callback)
  1329.     self.payload = payload or rawget(self, "payload")
  1330.     if callback then callback(self) end
  1331.     return self
  1332.   end,
  1333.  
  1334.   __index = function(self, key)
  1335.     local keys = extract_keys(key)
  1336.  
  1337.     -- execute modifiers and assertions
  1338.     local ret = nil
  1339.     for _, key in ipairs(keys) do
  1340.       if namespace.modifier[key] then
  1341.         namespace.modifier[key].state = self
  1342.         ret = self(nil, namespace.modifier[key])
  1343.       elseif namespace.assertion[key] then
  1344.         namespace.assertion[key].state = self
  1345.         ret = namespace.assertion[key]
  1346.       end
  1347.     end
  1348.     return ret
  1349.   end
  1350. }
  1351.  
  1352. obj = {
  1353.   state = function() return setmetatable({mod=true, payload=nil}, __state_meta) end,
  1354.  
  1355.   -- registers a function in namespace
  1356.   register = function(self, nspace, name, callback, positive_message, negative_message)
  1357.     -- register
  1358.     local lowername = name:lower()
  1359.     if not namespace[nspace] then
  1360.       namespace[nspace] = {}
  1361.     end
  1362.     namespace[nspace][lowername] = setmetatable({
  1363.       callback = callback,
  1364.       name = lowername,
  1365.       positive_message=positive_message,
  1366.       negative_message=negative_message
  1367.     }, __assertion_meta)
  1368.   end,
  1369.  
  1370.   -- registers a formatter
  1371.   -- a formatter takes a single argument, and converts it to a string, or returns nil if it cannot format the argument
  1372.   add_formatter = function(self, callback)
  1373.     astate.add_formatter(callback)
  1374.   end,
  1375.  
  1376.   -- unregisters a formatter
  1377.   remove_formatter = function(self, fmtr)
  1378.     astate.remove_formatter(fmtr)
  1379.   end,
  1380.  
  1381.   format = function(self, args)
  1382.     -- args.n specifies the number of arguments in case of 'trailing nil' arguments which get lost
  1383.     local nofmt = args.nofmt or {}  -- arguments in this list should not be formatted
  1384.     for i = 1, (args.n or #args) do -- cannot use pairs because table might have nils
  1385.       if not nofmt[i] then
  1386.         local val = args[i]
  1387.         local valfmt = astate.format_argument(val)
  1388.         if valfmt == nil then valfmt = tostring(val) end -- no formatter found
  1389.         args[i] = valfmt
  1390.       end
  1391.     end
  1392.     return args
  1393.   end,
  1394.  
  1395.   set_parameter = function(self, name, value)
  1396.     astate.set_parameter(name, value)
  1397.   end,
  1398.  
  1399.   get_parameter = function(self, name)
  1400.     return astate.get_parameter(name)
  1401.   end,
  1402.  
  1403.   add_spy = function(self, spy)
  1404.     astate.add_spy(spy)
  1405.   end,
  1406.  
  1407.   snapshot = function(self)
  1408.     return astate.snapshot()
  1409.   end,
  1410. }
  1411.  
  1412. local __meta = {
  1413.  
  1414.   __call = function(self, bool, message, ...)
  1415.     if not bool then
  1416.       error(message or "assertion failed!", 2)
  1417.     end
  1418.     return bool , message , ...
  1419.   end,
  1420.  
  1421.   __index = function(self, key)
  1422.     return rawget(self, key) or self.state()[key]
  1423.   end,
  1424.  
  1425. }
  1426.  
  1427. return setmetatable(obj, __meta)
  1428. end)
  1429. local util=_W(function()
  1430. local util = {}
  1431. function util.deepcompare(t1,t2,ignore_mt)
  1432.   local ty1 = type(t1)
  1433.   local ty2 = type(t2)
  1434.   if ty1 ~= ty2 then return false end
  1435.   -- non-table types can be directly compared
  1436.   if ty1 ~= 'table' then return t1 == t2 end
  1437.   local mt1 = getmetatable(t1)
  1438.   local mt2 = getmetatable(t2)
  1439.   -- would equality be determined by metatable __eq?
  1440.   if mt1 and mt1 == mt2 and type(mt1) == "table" and mt1.__eq then
  1441.     -- then use that unless asked not to
  1442.     if not ignore_mt then return t1 == t2 end
  1443.   else -- we can skip the deep comparison below if t1 and t2 share identity
  1444.     if t1 == t2 then return true end
  1445.   end
  1446.   for k1,v1 in pairs(t1) do
  1447.     local v2 = t2[k1]
  1448.     if v2 == nil or not util.deepcompare(v1,v2) then return false end
  1449.   end
  1450.   for k2,_ in pairs(t2) do
  1451.     -- only check wether each element has a t1 counterpart, actual comparison
  1452.     -- has been done in first loop above
  1453.     if t1[k2] == nil then return false end
  1454.   end
  1455.   return true
  1456. end
  1457.  
  1458. -----------------------------------------------
  1459. -- table.insert() replacement that respects nil values.
  1460. -- The function will use table field 'n' as indicator of the
  1461. -- table length, if not set, it will be added.
  1462. -- @param t table into which to insert
  1463. -- @param pos (optional) position in table where to insert. NOTE: not optional if you want to insert a nil-value!
  1464. -- @param val value to insert
  1465. -- @return No return values
  1466. function util.tinsert(...)
  1467.   -- check optional POS value
  1468.   local args = {...}
  1469.   local c = select('#',...)
  1470.   local t = args[1]
  1471.   local pos = args[2]
  1472.   local val = args[3]
  1473.   if c < 3 then
  1474.     val = pos
  1475.     pos = nil
  1476.   end
  1477.   -- set length indicator n if not present (+1)
  1478.   t.n = (t.n or #t) + 1
  1479.   if not pos then
  1480.     pos = t.n
  1481.   elseif pos > t.n then
  1482.     -- out of our range
  1483.     t[pos] = val
  1484.     t.n = pos
  1485.   end
  1486.   -- shift everything up 1 pos
  1487.   for i = t.n, pos + 1, -1 do
  1488.     t[i]=t[i-1]
  1489.   end
  1490.   -- add element to be inserted
  1491.   t[pos] = val
  1492. end
  1493. -----------------------------------------------
  1494. -- table.remove() replacement that respects nil values.
  1495. -- The function will use table field 'n' as indicator of the
  1496. -- table length, if not set, it will be added.
  1497. -- @param t table from which to remove
  1498. -- @param pos (optional) position in table to remove
  1499. -- @return No return values
  1500. function util.tremove(t, pos)
  1501.   -- set length indicator n if not present (+1)
  1502.   t.n = t.n or #t
  1503.   if not pos then
  1504.     pos = t.n
  1505.   elseif pos > t.n then
  1506.     -- out of our range
  1507.     t[pos] = nil
  1508.     return
  1509.   end
  1510.   -- shift everything up 1 pos
  1511.   for i = pos, t.n do
  1512.     t[i]=t[i+1]
  1513.   end
  1514.   -- set size, clean last
  1515.   t[t.n] = nil
  1516.   t.n = t.n - 1
  1517. end
  1518.  
  1519. -----------------------------------------------
  1520. -- Checks an element to be callable.
  1521. -- The type must either be a function or have a metatable
  1522. -- containing an '__call' function.
  1523. -- @param object element to inspect on being callable or not
  1524. -- @return boolean, true if the object is callable
  1525. function util.callable(object)
  1526.   return type(object) == "function" or (type(object) == "table" and type((getmetatable(object) or {}).__call) == "function")
  1527. end
  1528.  
  1529. return util
  1530. end)
  1531. do
  1532. -- module will not return anything, only register assertions with the main assert engine
  1533.  
  1534. -- assertions take 2 parameters;
  1535. -- 1) state
  1536. -- 2) arguments list. The list has a member 'n' with the argument count to check for trailing nils
  1537. -- returns; boolean; whether assertion passed
  1538.  
  1539. local s = say
  1540.  
  1541. local function unique(state, arguments)
  1542.   local list = arguments[1]
  1543.   local deep = arguments[2]
  1544.   for k,v in pairs(list) do
  1545.     for k2, v2 in pairs(list) do
  1546.       if k ~= k2 then
  1547.         if deep and util.deepcompare(v, v2, true) then
  1548.           return false
  1549.         else
  1550.           if v == v2 then
  1551.             return false
  1552.           end
  1553.         end
  1554.       end
  1555.     end
  1556.   end
  1557.   return true
  1558. end
  1559.  
  1560. local function equals(state, arguments)
  1561.   local argcnt = arguments.n
  1562.   assert(argcnt > 1, s("assertion.internal.argtolittle", { "equals", 2, tostring(argcnt) }))
  1563.   for i = 2,argcnt  do
  1564.     if arguments[1] ~= arguments[i] then
  1565.       -- switch arguments for proper output message
  1566.       util.tinsert(arguments, 1, arguments[i])
  1567.       util.tremove(arguments, i + 1)
  1568.       return false
  1569.     end
  1570.   end
  1571.   return true
  1572. end
  1573.  
  1574. local function same(state, arguments)
  1575.   local argcnt = arguments.n
  1576.   assert(argcnt > 1, s("assertion.internal.argtolittle", { "same", 2, tostring(argcnt) }))
  1577.   local prev = nil
  1578.   for i = 2,argcnt  do
  1579.     if type(arguments[1]) == 'table' and type(arguments[i]) == 'table' then
  1580.       if not util.deepcompare(arguments[1], arguments[i], true) then
  1581.         -- switch arguments for proper output message
  1582.         util.tinsert(arguments, 1, arguments[i])
  1583.         util.tremove(arguments, i + 1)
  1584.         return false
  1585.       end
  1586.     else
  1587.       if arguments[1] ~= arguments[i] then
  1588.         -- switch arguments for proper output message
  1589.         util.tinsert(arguments, 1, arguments[i])
  1590.         util.tremove(arguments, i + 1)
  1591.         return false
  1592.       end
  1593.     end
  1594.   end
  1595.   return true
  1596. end
  1597.  
  1598. local function truthy(state, arguments)
  1599.   return arguments[1] ~= false and arguments[1] ~= nil
  1600. end
  1601.  
  1602. local function falsy(state, arguments)
  1603.   return not truthy(state, arguments)
  1604. end
  1605.  
  1606. local function has_error(state, arguments)
  1607.   local func = arguments[1]
  1608.   local err_expected = arguments[2]
  1609.   assert(util.callable(func), s("assertion.internal.badargtype", { "error", "function, or callable object", type(func) }))
  1610.   local ok, err_actual = pcall(func)
  1611.   arguments[1] = err_actual
  1612.   arguments[2] = err_expected
  1613.   if ok or err_expected == nil then
  1614.     return not ok
  1615.   elseif type(err_actual) == 'string' and type(err_expected) == 'string' then
  1616.     return err_actual:find(err_expected, nil, true) ~= nil
  1617.   end
  1618.   return same(state, {err_expected, err_actual, ["n"] = 2})
  1619. end
  1620.  
  1621. local function is_true(state, arguments)
  1622.   util.tinsert(arguments, 2, true)
  1623.   arguments.n = arguments.n + 1
  1624.   return arguments[1] == arguments[2]
  1625. end
  1626.  
  1627. local function is_false(state, arguments)
  1628.   util.tinsert(arguments, 2, false)
  1629.   arguments.n = arguments.n + 1
  1630.   return arguments[1] == arguments[2]
  1631. end
  1632.  
  1633. local function is_type(state, arguments, etype)
  1634.   util.tinsert(arguments, 2, "type " .. etype)
  1635.   arguments.nofmt = arguments.nofmt or {}
  1636.   arguments.nofmt[2] = true
  1637.   arguments.n = arguments.n + 1
  1638.   return arguments.n > 1 and type(arguments[1]) == etype
  1639. end
  1640.  
  1641. local function returned_arguments(state, arguments)
  1642.   arguments[1] = tostring(arguments[1])
  1643.   arguments[2] = tostring(arguments.n - 1)
  1644.   arguments.nofmt = arguments.nofmt or {}
  1645.   arguments.nofmt[1] = true
  1646.   arguments.nofmt[2] = true
  1647.   if arguments.n < 2 then arguments.n = 2 end
  1648.   return arguments[1] == arguments[2]
  1649. end
  1650.  
  1651. local function is_boolean(state, arguments)  return is_type(state, arguments, "boolean")  end
  1652. local function is_number(state, arguments)   return is_type(state, arguments, "number")   end
  1653. local function is_string(state, arguments)   return is_type(state, arguments, "string")   end
  1654. local function is_table(state, arguments)    return is_type(state, arguments, "table")    end
  1655. local function is_nil(state, arguments)      return is_type(state, arguments, "nil")      end
  1656. local function is_userdata(state, arguments) return is_type(state, arguments, "userdata") end
  1657. local function is_function(state, arguments) return is_type(state, arguments, "function") end
  1658. local function is_thread(state, arguments)   return is_type(state, arguments, "thread")   end
  1659.  
  1660. assert:register("assertion", "true", is_true, "assertion.same.positive", "assertion.same.negative")
  1661. assert:register("assertion", "false", is_false, "assertion.same.positive", "assertion.same.negative")
  1662. assert:register("assertion", "boolean", is_boolean, "assertion.same.positive", "assertion.same.negative")
  1663. assert:register("assertion", "number", is_number, "assertion.same.positive", "assertion.same.negative")
  1664. assert:register("assertion", "string", is_string, "assertion.same.positive", "assertion.same.negative")
  1665. assert:register("assertion", "table", is_table, "assertion.same.positive", "assertion.same.negative")
  1666. assert:register("assertion", "nil", is_nil, "assertion.same.positive", "assertion.same.negative")
  1667. assert:register("assertion", "userdata", is_userdata, "assertion.same.positive", "assertion.same.negative")
  1668. assert:register("assertion", "function", is_function, "assertion.same.positive", "assertion.same.negative")
  1669. assert:register("assertion", "thread", is_thread, "assertion.same.positive", "assertion.same.negative")
  1670. assert:register("assertion", "returned_arguments", returned_arguments, "assertion.returned_arguments.positive", "assertion.returned_arguments.negative")
  1671.  
  1672. assert:register("assertion", "same", same, "assertion.same.positive", "assertion.same.negative")
  1673. assert:register("assertion", "equals", equals, "assertion.equals.positive", "assertion.equals.negative")
  1674. assert:register("assertion", "equal", equals, "assertion.equals.positive", "assertion.equals.negative")
  1675. assert:register("assertion", "unique", unique, "assertion.unique.positive", "assertion.unique.negative")
  1676. assert:register("assertion", "error", has_error, "assertion.error.positive", "assertion.error.negative")
  1677. assert:register("assertion", "errors", has_error, "assertion.error.positive", "assertion.error.negative")
  1678. assert:register("assertion", "truthy", truthy, "assertion.truthy.positive", "assertion.truthy.negative")
  1679. assert:register("assertion", "falsy", falsy, "assertion.falsy.positive", "assertion.falsy.negative")
  1680. end
  1681. do
  1682. -- module will not return anything, only register assertions/modifiers with the main assert engine
  1683.  
  1684. local function is(state)
  1685.   return state
  1686. end
  1687.  
  1688. local function is_not(state)
  1689.   state.mod = not state.mod
  1690.   return state
  1691. end
  1692.  
  1693. assert:register("modifier", "is", is)
  1694. assert:register("modifier", "are", is)
  1695. assert:register("modifier", "was", is)
  1696. assert:register("modifier", "has", is)
  1697. assert:register("modifier", "not", is_not)
  1698. assert:register("modifier", "no", is_not)
  1699. end
  1700. do
  1701. -- module will not return anything, only register formatters with the main assert engine
  1702. local function fmt_string(arg)
  1703.   if type(arg) == "string" then
  1704.     return string.format("(string) '%s'", arg)
  1705.   end
  1706. end
  1707.  
  1708. local function fmt_number(arg)
  1709.   if type(arg) == "number" then
  1710.     return string.format("(number) %s", tostring(arg))
  1711.   end
  1712. end
  1713.  
  1714. local function fmt_boolean(arg)
  1715.   if type(arg) == "boolean" then
  1716.     return string.format("(boolean) %s", tostring(arg))
  1717.   end
  1718. end
  1719.  
  1720. local function fmt_nil(arg)
  1721.   if type(arg) == "nil" then
  1722.     return "(nil)"
  1723.   end
  1724. end
  1725.  
  1726. local type_priorities = {
  1727.   number = 1,
  1728.   boolean = 2,
  1729.   string = 3,
  1730.   table = 4,
  1731.   ["function"] = 5,
  1732.   userdata = 6,
  1733.   thread = 7
  1734. }
  1735.  
  1736. local function is_in_array_part(key, length)
  1737.   return type(key) == "number" and 1 <= key and key <= length and math.floor(key) == key
  1738. end
  1739.  
  1740. local function get_sorted_keys(t)
  1741.   local keys = {}
  1742.   local nkeys = 0
  1743.  
  1744.   for key in pairs(t) do
  1745.     nkeys = nkeys + 1
  1746.     keys[nkeys] = key
  1747.   end
  1748.  
  1749.   local length = #t
  1750.  
  1751.   local function key_comparator(key1, key2)
  1752.     local type1, type2 = type(key1), type(key2)
  1753.     local priority1 = is_in_array_part(key1, length) and 0 or type_priorities[type1] or 8
  1754.     local priority2 = is_in_array_part(key2, length) and 0 or type_priorities[type2] or 8
  1755.  
  1756.     if priority1 == priority2 then
  1757.       if type1 == "string" or type1 == "number" then
  1758.         return key1 < key2
  1759.       elseif type1 == "boolean" then
  1760.         return key1  -- put true before false
  1761.       end
  1762.     else
  1763.       return priority1 < priority2
  1764.     end
  1765.   end
  1766.  
  1767.   table.sort(keys, key_comparator)
  1768.   return keys, nkeys
  1769. end
  1770.  
  1771. local function fmt_table(arg)
  1772.   local tmax = assert:get_parameter("TableFormatLevel")
  1773.   local ft
  1774.   ft = function(t, l)
  1775.     local result = ""
  1776.     local keys, nkeys = get_sorted_keys(t)
  1777.     for i = 1, nkeys do
  1778.       local k = keys[i]
  1779.       local v = t[k]
  1780.       if type(v) == "table" then
  1781.         if l < tmax or tmax < 0 then
  1782.           result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = {\n%s }\n", tostring(k), tostring(ft(v, l + 1):sub(1,-2)))
  1783.         else
  1784.           result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = { ... more }\n", tostring(k))
  1785.         end
  1786.       else
  1787.         if type(v) == "string" then v = "'"..v.."'" end
  1788.         result = result .. string.format(string.rep(" ",l * 2) .. "[%s] = %s\n", tostring(k), tostring(v))
  1789.       end
  1790.     end
  1791.     return result
  1792.   end
  1793.   if type(arg) == "table" then
  1794.     local result
  1795.     if tmax == 0 then
  1796.       if next(arg) then
  1797.         result = "(table): { ... more }"
  1798.       else
  1799.         result = "(table): { }"
  1800.       end
  1801.     else
  1802.       result = "(table): {\n" .. ft(arg, 1):sub(1,-2) .. " }\n"
  1803.       result = result:gsub("{\n }\n", "{ }\n") -- cleanup empty tables
  1804.       result = result:sub(1,-2)                -- remove trailing newline
  1805.     end
  1806.     return result
  1807.   end
  1808. end
  1809.  
  1810. local function fmt_function(arg)
  1811.   if type(arg) == "function" then
  1812.     --local debug_info = debug.getinfo(arg)
  1813.     --return string.format("%s @ line %s in %s", tostring(arg), tostring(debug_info.linedefined), tostring(debug_info.source))
  1814.     return tostring(arg)
  1815.   end
  1816. end
  1817.  
  1818. local function fmt_userdata(arg)
  1819.   if type(arg) == "userdata" then
  1820.     return string.format("(userdata) '%s'", tostring(arg))
  1821.   end
  1822. end
  1823.  
  1824. local function fmt_thread(arg)
  1825.   if type(arg) == "thread" then
  1826.     return string.format("(thread) '%s'", tostring(arg))
  1827.   end
  1828. end
  1829.  
  1830. assert:add_formatter(fmt_string)
  1831. assert:add_formatter(fmt_number)
  1832. assert:add_formatter(fmt_boolean)
  1833. assert:add_formatter(fmt_nil)
  1834. assert:add_formatter(fmt_table)
  1835. assert:add_formatter(fmt_function)
  1836. assert:add_formatter(fmt_userdata)
  1837. assert:add_formatter(fmt_thread)
  1838. -- Set default table display depth for table formatter
  1839. assert:set_parameter("TableFormatLevel", 3)
  1840. end
  1841. do
  1842. local s = say
  1843.  
  1844. s:set_namespace('en')
  1845.  
  1846. s:set("assertion.same.positive", "Expected objects to be the same.\nPassed in:\n%s\nExpected:\n%s")
  1847. s:set("assertion.same.negative", "Expected objects to not be the same.\nPassed in:\n%s\nDid not expect:\n%s")
  1848.  
  1849. s:set("assertion.equals.positive", "Expected objects to be equal.\nPassed in:\n%s\nExpected:\n%s")
  1850. s:set("assertion.equals.negative", "Expected objects to not be equal.\nPassed in:\n%s\nDid not expect:\n%s")
  1851.  
  1852. s:set("assertion.unique.positive", "Expected object to be unique:\n%s")
  1853. s:set("assertion.unique.negative", "Expected object to not be unique:\n%s")
  1854.  
  1855. s:set("assertion.error.positive", "Expected a different error.\nCaught:\n%s\nExpected:\n%s")
  1856. s:set("assertion.error.negative", "Expected no error, but caught:\n%s")
  1857.  
  1858. s:set("assertion.truthy.positive", "Expected to be truthy, but value was:\n%s")
  1859. s:set("assertion.truthy.negative", "Expected to not be truthy, but value was:\n%s")
  1860.  
  1861. s:set("assertion.falsy.positive", "Expected to be falsy, but value was:\n%s")
  1862. s:set("assertion.falsy.negative", "Expected to not be falsy, but value was:\n%s")
  1863.  
  1864. s:set("assertion.called.positive", "Expected to be called %s time(s), but was called %s time(s)")
  1865. s:set("assertion.called.negative", "Expected not to be called exactly %s time(s), but it was.")
  1866.  
  1867. s:set("assertion.called_with.positive", "Function was not called with the arguments")
  1868. s:set("assertion.called_with.negative", "Function was called with the arguments")
  1869.  
  1870. s:set("assertion.returned_arguments.positive", "Expected to be called with %s argument(s), but was called with %s")
  1871. s:set("assertion.returned_arguments.negative", "Expected not to be called with %s argument(s), but was called with %s")
  1872.  
  1873. -- errors
  1874. s:set("assertion.internal.argtolittle", "the '%s' function requires a minimum of %s arguments, got: %s")
  1875. s:set("assertion.internal.badargtype", "the '%s' function requires a %s as an argument, got: %s")
  1876. end
  1877. local luassert=_W(function()
  1878. assert._COPYRIGHT   = "Copyright (c) 2012 Olivine Labs, LLC."
  1879. assert._DESCRIPTION = "Extends Lua's built-in assertions to provide additional tests and the ability to create your own."
  1880. assert._VERSION     = "Luassert 1.4"
  1881.  
  1882. return assert
  1883. end)
  1884. local outputHandlerLoader=_W(function()
  1885. local terminals = {
  1886.     colorTerminal = colorTerminal,
  1887. }
  1888.  
  1889. return function()
  1890.   local loadOutputHandler = function(output, options, busted, defaultOutput)
  1891.     local func
  1892.     if type(output) == "function" then
  1893.         func = output(options, busted)
  1894.     else
  1895.         func = terminals[output] or dofile(output)
  1896.     end
  1897.     return func(options, busted)
  1898.   end
  1899.  
  1900.   return loadOutputHandler
  1901. end
  1902. end)
  1903. local environment=_W(function()
  1904. return function(context)
  1905.  
  1906.   local environment = {}
  1907.  
  1908.   local function getEnv(self, key)
  1909.     if not self then return nil end
  1910.     return
  1911.       self.env and self.env[key] or
  1912.       getEnv(context.parent(self), key) or
  1913.       _G[key]
  1914.   end
  1915.  
  1916.   local function setEnv(self, key, value)
  1917.     if not self.env then self.env = {} end
  1918.     self.env[key] = value
  1919.   end
  1920.  
  1921.   local function __index(self, key)
  1922.     return getEnv(context.get(), key)
  1923.   end
  1924.  
  1925.   local function __newindex(self, key, value)
  1926.     setEnv(context.get(), key, value)
  1927.   end
  1928.  
  1929.   local env = setmetatable({}, { __index=__index, __newindex=__newindex })
  1930.  
  1931.   function environment.wrap(fn)
  1932.     return setfenv(fn, env)
  1933.   end
  1934.  
  1935.   function environment.set(key, value)
  1936.     local env = context.get('env')
  1937.  
  1938.     if not env then
  1939.       env = {}
  1940.       context.set('env', env)
  1941.     end
  1942.  
  1943.     env[key] = value
  1944.   end
  1945.   return environment
  1946. end
  1947. end)
  1948. local spy=_W(function()
  1949. -- module will return spy table, and register its assertions with the main assert engine
  1950.  
  1951. -- Spy metatable
  1952. local spy_mt = {
  1953.   __call = function(self, ...)
  1954.     local arguments = {...}
  1955.     arguments.n = select('#',...)  -- add argument count for trailing nils
  1956.     table.insert(self.calls, arguments)
  1957.     return self.callback(...)
  1958.   end }
  1959.  
  1960. local spy   -- must make local before defining table, because table contents refers to the table (recursion)
  1961. spy = {
  1962.   new = function(callback)
  1963.     if not util.callable(callback) then
  1964.       error("Cannot spy on type '" .. type(callback) .. "', only on functions or callable elements", 2)
  1965.     end
  1966.     local s = setmetatable(
  1967.     {
  1968.       calls = {},
  1969.       callback = callback,
  1970.  
  1971.       target_table = nil, -- these will be set when using 'spy.on'
  1972.       target_key = nil,
  1973.  
  1974.       revert = function(self)
  1975.         if not self.reverted then
  1976.           if self.target_table and self.target_key then
  1977.             self.target_table[self.target_key] = self.callback
  1978.           end
  1979.           self.reverted = true
  1980.         end
  1981.         return self.callback
  1982.       end,
  1983.  
  1984.       called = function(self, times)
  1985.         if times then
  1986.           return (#self.calls == times), #self.calls
  1987.         end
  1988.  
  1989.         return (#self.calls > 0), #self.calls
  1990.       end,
  1991.  
  1992.       called_with = function(self, args)
  1993.         for _,v in ipairs(self.calls) do
  1994.           if util.deepcompare(v, args) then
  1995.             return true
  1996.           end
  1997.         end
  1998.         return false
  1999.       end
  2000.     }, spy_mt)
  2001.     assert:add_spy(s)  -- register with the current state
  2002.     return s
  2003.   end,
  2004.  
  2005.   is_spy = function(object)
  2006.     return type(object) == "table" and getmetatable(object) == spy_mt
  2007.   end,
  2008.  
  2009.   on = function(target_table, target_key)
  2010.     local s = spy.new(target_table[target_key])
  2011.     target_table[target_key] = s
  2012.     -- store original data
  2013.     s.target_table = target_table
  2014.     s.target_key = target_key
  2015.  
  2016.     return s
  2017.   end
  2018. }
  2019.  
  2020. local function set_spy(state)
  2021. end
  2022.  
  2023. local function called_with(state, arguments)
  2024.   if rawget(state, "payload") and rawget(state, "payload").called_with then
  2025.     return state.payload:called_with(arguments)
  2026.   else
  2027.     error("'called_with' must be chained after 'spy(aspy)'")
  2028.   end
  2029. end
  2030.  
  2031. local function called(state, arguments)
  2032.   local num_times = arguments[1]
  2033.   if state.payload and type(state.payload) == "table" and state.payload.called then
  2034.     local result, count = state.payload:called(num_times)
  2035.     arguments[1] = tostring(arguments[1])
  2036.     table.insert(arguments, 2, tostring(count))
  2037.     arguments.n = arguments.n + 1
  2038.     arguments.nofmt = arguments.nofmt or {}
  2039.     arguments.nofmt[1] = true
  2040.     arguments.nofmt[2] = true
  2041.     return result
  2042.   elseif state.payload and type(state.payload) == "function" then
  2043.     error("When calling 'spy(aspy)', 'aspy' must not be the original function, but the spy function replacing the original")
  2044.   else
  2045.     error("'called_with' must be chained after 'spy(aspy)'")
  2046.   end
  2047. end
  2048.  
  2049. assert:register("modifier", "spy", set_spy)
  2050. assert:register("assertion", "called_with", called_with, "assertion.called_with.positive", "assertion.called_with.negative")
  2051. assert:register("assertion", "called", called, "assertion.called.positive", "assertion.called.negative")
  2052.  
  2053. return spy
  2054. end)
  2055. local stub=_W(function()
  2056. -- module will return a stub module table
  2057. local stubfunc = function() end
  2058. local stub = {}
  2059.  
  2060. function stub.new(object, key)
  2061.   if object == nil and key == nil then
  2062.     -- called without arguments, create a 'blank' stub
  2063.     object = {}
  2064.     key = ""
  2065.   end
  2066.   assert(type(object) == "table" and key ~= nil, "stub.new(): Can only create stub on a table key, call with 2 params; table, key")
  2067.   assert(object[key] == nil or util.callable(object[key]), "stub.new(): The element for which to create a stub must either be callable, or be nil")
  2068.   local old_elem = object[key]    -- keep existing element (might be nil!)
  2069.   object[key] = stubfunc          -- set the stubfunction
  2070.   local s = spy.on(object, key)   -- create a spy on top of the stub function
  2071.   local spy_revert = s.revert     -- keep created revert function
  2072.  
  2073.   s.revert = function(self)       -- wrap revert function to restore original element
  2074.     if not self.reverted then
  2075.       spy_revert(self)
  2076.       object[key] = old_elem
  2077.       self.reverted = true
  2078.     end
  2079.     return old_elem
  2080.   end
  2081.  
  2082.   return s
  2083. end
  2084.  
  2085. function stub.is_stub(object)
  2086.   return spy.is_spy(object) and object.callback == stubfunc
  2087. end
  2088.  
  2089. local function set_stub(state)
  2090. end
  2091.  
  2092. assert:register("modifier", "stub", set_stub)
  2093.  
  2094. return setmetatable( stub, {
  2095.     __call = function(self, ...)
  2096.       -- stub originally was a function only. Now that it is a module table
  2097.       -- the __call method is required for backward compatibility
  2098.       -- NOTE: this deviates from spy, which has no __call method
  2099.       return stub.new(...)
  2100.     end })
  2101. end)
  2102. local configurationLoader=_W(function()
  2103. local function merge(old, new)
  2104.   local r = {}
  2105.   for k, v in pairs(old) do r[k]=v end
  2106.   for k, v in pairs(new) do r[k]=v end
  2107.   return r
  2108. end
  2109.  
  2110. return function()
  2111.   -- Function to load the .busted configuration file if available
  2112.   local loadBustedConfigurationFile = function(configFile, config)
  2113.     if type(configFile) ~= 'table' then
  2114.       return config, '.busted file does not return a table.'
  2115.     end
  2116.  
  2117.     local run = config.run
  2118.  
  2119.     if run and run ~= '' then
  2120.       local runConfig = configFile[run]
  2121.  
  2122.       if type(runConfig) == 'table' then
  2123.  
  2124.         config = merge(config, runConfig)
  2125.         return config
  2126.       else
  2127.         return config, 'Task `' .. run .. '` not found, or not a table.'
  2128.       end
  2129.     end
  2130.  
  2131.     if configFile and type(configFile.default) == 'table' then
  2132.       config = merge(config, configFile.default)
  2133.     end
  2134.  
  2135.     return config
  2136.   end
  2137.  
  2138.   return loadBustedConfigurationFile
  2139. end
  2140.  
  2141. end)
  2142. local fileType_Lua=_W(function()
  2143. local ret = {}
  2144.  
  2145. local getTrace =  function(filename, info)
  2146.   -- local index = info.traceback:find('\n%s*%[C]')
  2147.   -- info.traceback = info.traceback:sub(1, index)
  2148.   return info, false
  2149. end
  2150.  
  2151. ret.match = function(busted, filename)
  2152.   local path, name, ext = filename:match('(.-)([^\\/\\\\]-%.?([^%.\\/]*))$')
  2153.   if ext == 'lua' or ext == "" or ext == nil then
  2154.     return true
  2155.   end
  2156.   return false
  2157. end
  2158.  
  2159.  
  2160. ret.load = function(busted, filename)
  2161.   local file, err = loadfile(filename)
  2162.   if not file then
  2163.     busted.publish({ 'error', 'file' }, { descriptor = 'file', name = filename }, nil, err, {})
  2164.   end
  2165.   return file, getTrace
  2166. end
  2167.  
  2168. return ret
  2169. end)
  2170. local testFileLoader=_W(function()
  2171. local fileLoaderTypes = {
  2172.   lua = fileType_Lua,
  2173. }
  2174.  
  2175. return function(busted, loaders)
  2176.   local fileLoaders = {}
  2177.   for _, l in ipairs(loaders) do
  2178.     local loader
  2179.     if type(l) == "table" then
  2180.       loader = l
  2181.     elseif type(l) == "function" then
  2182.       loader = l()
  2183.     else
  2184.       loader = fileLoaderTypes[l] or dofile(l)
  2185.     end
  2186.     fileLoaders[#fileLoaders + 1] = loader
  2187.   end
  2188.   local getTestFiles = function(rootFile, pattern)
  2189.     local fileList = {}
  2190.  
  2191.     local add
  2192.     function add(file)
  2193.       if fs.isDir(file) then
  2194.         for _, v in ipairs(fs.list(file)) do
  2195.           add(fs.combine(file, v))
  2196.         end
  2197.       else
  2198.         if not file:find('/%.%w+.%w+') and fs.getName(file):find(pattern) then
  2199.           table.insert(fileList, file)
  2200.         end
  2201.       end
  2202.     end
  2203.     add(rootFile)
  2204.     table.sort(fileList)
  2205.     return fileList
  2206.   end
  2207.  
  2208.   -- runs a testfile, loading its tests
  2209.   local loadTestFile = function(busted, filename)
  2210.     for _, v in pairs(fileLoaders) do
  2211.       if v.match(busted, filename) then
  2212.         return v.load(busted, filename)
  2213.       end
  2214.     end
  2215.   end
  2216.  
  2217.   local loadTestFiles = function(rootFile, pattern, loaders)
  2218.     local fileList = getTestFiles(rootFile, pattern)
  2219.  
  2220.     for i, fileName in pairs(fileList) do
  2221.       local testFile, getTrace, rewriteMessage = loadTestFile(busted, fileName, loaders)
  2222.  
  2223.       if testFile then
  2224.         local file = setmetatable({
  2225.           getTrace = getTrace,
  2226.           rewriteMessage = rewriteMessage
  2227.         }, {
  2228.           __call = testFile
  2229.         })
  2230.  
  2231.         busted.executors.file(fileName, file)
  2232.       end
  2233.     end
  2234.  
  2235.     return fileList
  2236.   end
  2237.  
  2238.   return loadTestFiles, loadTestFile, getTestFiles
  2239. end
  2240.  
  2241. end)
  2242. local context=_W(function()
  2243. return function()
  2244.   local context = {}
  2245.  
  2246.   local data = {}
  2247.   local parents = {}
  2248.   local children = {}
  2249.  
  2250.   function context.ref()
  2251.     local ref = {}
  2252.     local ctx = data
  2253.  
  2254.     function ref.get(key)
  2255.       if not key then return ctx end
  2256.       return ctx[key]
  2257.     end
  2258.  
  2259.     function ref.set(key, value)
  2260.       ctx[key] = value
  2261.     end
  2262.  
  2263.     function ref.attach(child)
  2264.       if not children[ctx] then children[ctx] = {} end
  2265.       parents[child] = ctx
  2266.       children[ctx][#children[ctx]+1] = child
  2267.     end
  2268.  
  2269.     function ref.children(parent)
  2270.       return children[parent] or {}
  2271.     end
  2272.  
  2273.     function ref.parent(child)
  2274.       return parents[child]
  2275.     end
  2276.  
  2277.     function ref.push(child)
  2278.       if not parents[child] then error('Detached child. Cannot push.') end
  2279.       ctx = child
  2280.     end
  2281.  
  2282.     function ref.pop()
  2283.       ctx = parents[ctx]
  2284.     end
  2285.  
  2286.     return ref
  2287.   end
  2288.  
  2289.   return context
  2290. end
  2291. end)
  2292. local mediator=_W(function()
  2293. local function Subscriber(fn, options)
  2294.   return {
  2295.     options = options or {},
  2296.     fn = fn,
  2297.     channel = nil,
  2298.     id = math.random(1000000000), -- sounds reasonable, rite?
  2299.     update = function(self, options)
  2300.       if options then
  2301.         self.fn = options.fn or self.fn
  2302.         self.options = options.options or self.options
  2303.       end
  2304.     end
  2305.   }
  2306. end
  2307.  
  2308. -- Channel class and functions --
  2309.  
  2310. local function Channel(namespace, parent)
  2311.   return {
  2312.     stopped = false,
  2313.     namespace = namespace,
  2314.     callbacks = {},
  2315.     channels = {},
  2316.     parent = parent,
  2317.  
  2318.     addSubscriber = function(self, fn, options)
  2319.       local callback = Subscriber(fn, options)
  2320.       local priority = (#self.callbacks + 1)
  2321.  
  2322.       options = options or {}
  2323.  
  2324.       if options.priority and
  2325.         options.priority >= 0 and
  2326.         options.priority < priority
  2327.       then
  2328.           priority = options.priority
  2329.       end
  2330.  
  2331.       table.insert(self.callbacks, priority, callback)
  2332.  
  2333.       return callback
  2334.     end,
  2335.  
  2336.     getSubscriber = function(self, id)
  2337.       for i=1, #self.callbacks do
  2338.         local callback = self.callbacks[i]
  2339.         if callback.id == id then return { index = i, value = callback } end
  2340.       end
  2341.       local sub
  2342.       for _, channel in pairs(self.channels) do
  2343.         sub = channel:getSubscriber(id)
  2344.         if sub then break end
  2345.       end
  2346.       return sub
  2347.     end,
  2348.  
  2349.     setPriority = function(self, id, priority)
  2350.       local callback = self:getSubscriber(id)
  2351.  
  2352.       if callback.value then
  2353.         table.remove(self.callbacks, callback.index)
  2354.         table.insert(self.callbacks, priority, callback.value)
  2355.       end
  2356.     end,
  2357.  
  2358.     addChannel = function(self, namespace)
  2359.       self.channels[namespace] = Channel(namespace, self)
  2360.       return self.channels[namespace]
  2361.     end,
  2362.  
  2363.     hasChannel = function(self, namespace)
  2364.       return namespace and self.channels[namespace] and true
  2365.     end,
  2366.  
  2367.     getChannel = function(self, namespace)
  2368.       return self.channels[namespace] or self:addChannel(namespace)
  2369.     end,
  2370.  
  2371.     removeSubscriber = function(self, id)
  2372.       local callback = self:getSubscriber(id)
  2373.  
  2374.       if callback and callback.value then
  2375.         for _, channel in pairs(self.channels) do
  2376.           channel:removeSubscriber(id)
  2377.         end
  2378.  
  2379.         return table.remove(self.callbacks, callback.index)
  2380.       end
  2381.     end,
  2382.  
  2383.     publish = function(self, result, ...)
  2384.       for i = 1, #self.callbacks do
  2385.         local callback = self.callbacks[i]
  2386.  
  2387.         -- if it doesn't have a predicate, or it does and it's true then run it
  2388.         if not callback.options.predicate or callback.options.predicate(...) then
  2389.            -- just take the first result and insert it into the result table
  2390.           local value, continue = callback.fn(...)
  2391.  
  2392.           if value then table.insert(result, value) end
  2393.           if not continue then return result end
  2394.         end
  2395.       end
  2396.  
  2397.       if parent then
  2398.         return parent:publish(result, ...)
  2399.       else
  2400.         return result
  2401.       end
  2402.     end
  2403.   }
  2404. end
  2405.  
  2406. -- Mediator class and functions --
  2407.  
  2408. local Mediator = setmetatable(
  2409. {
  2410.   Channel = Channel,
  2411.   Subscriber = Subscriber
  2412. },
  2413. {
  2414.   __call = function (fn, options)
  2415.     return {
  2416.       channel = Channel('root'),
  2417.  
  2418.       getChannel = function(self, channelNamespace)
  2419.         local channel = self.channel
  2420.  
  2421.         for i=1, #channelNamespace do
  2422.           channel = channel:getChannel(channelNamespace[i])
  2423.         end
  2424.  
  2425.         return channel
  2426.       end,
  2427.  
  2428.       subscribe = function(self, channelNamespace, fn, options)
  2429.         return self:getChannel(channelNamespace):addSubscriber(fn, options)
  2430.       end,
  2431.  
  2432.       getSubscriber = function(self, id, channelNamespace)
  2433.         return self:getChannel(channelNamespace):getSubscriber(id)
  2434.       end,
  2435.  
  2436.       removeSubscriber = function(self, id, channelNamespace)
  2437.         return self:getChannel(channelNamespace):removeSubscriber(id)
  2438.       end,
  2439.  
  2440.       publish = function(self, channelNamespace, ...)
  2441.         return self:getChannel(channelNamespace):publish({}, ...)
  2442.       end
  2443.     }
  2444.   end
  2445. })
  2446. return Mediator
  2447. end)
  2448. local status=_W(function()
  2449. local function get_status(status)
  2450.   local smap = {
  2451.     ['success'] = 'success',
  2452.     ['pending'] = 'pending',
  2453.     ['failure'] = 'failure',
  2454.     ['error'] = 'error',
  2455.     ['true'] = 'success',
  2456.     ['false'] = 'failure',
  2457.     ['nil'] = 'error',
  2458.   }
  2459.   return smap[tostring(status)] or 'error'
  2460. end
  2461.  
  2462. return function(inital_status)
  2463.   local objstat = get_status(inital_status)
  2464.   local obj = {
  2465.     success = function(self) return (objstat == 'success') end,
  2466.     pending = function(self) return (objstat == 'pending') end,
  2467.     failure = function(self) return (objstat == 'failure') end,
  2468.     error   = function(self) return (objstat == 'error') end,
  2469.  
  2470.     get = function(self)
  2471.       return objstat
  2472.     end,
  2473.  
  2474.     set = function(self, status)
  2475.       objstat = get_status(status)
  2476.     end,
  2477.  
  2478.     update = function(self, status)
  2479.       -- prefer current failure/error status over new status
  2480.       status = get_status(status)
  2481.       if objstat == 'success' or (objstat == 'pending' and status ~= 'success') then
  2482.         objstat = status
  2483.       end
  2484.     end
  2485.   }
  2486.  
  2487.   return setmetatable(obj, {
  2488.     __index = {},
  2489.     __tostring = function(self) return objstat end
  2490.   })
  2491. end
  2492. end)
  2493. local mock=_W(function()
  2494. -- module will return a single mock function, no table nor register any assertions
  2495.  
  2496. local function mock(object, dostub, func, self, key)
  2497.   local data_type = type(object)
  2498.   if data_type == "table" then
  2499.     if spy.is_spy(object) then
  2500.       -- this table is a function already wrapped as a spy, so nothing to do here
  2501.     else
  2502.       for k,v in pairs(object) do
  2503.         object[k] = mock(v, dostub, func, object, k)
  2504.       end
  2505.     end
  2506.   elseif data_type == "function" then
  2507.     if dostub then
  2508.       return stub(self, key, func)
  2509.     elseif self==nil then
  2510.       return spy.new(object)
  2511.     else
  2512.       return spy.on(self, key)
  2513.     end
  2514.   end
  2515.   return object
  2516. end
  2517.  
  2518. return mock
  2519. end)
  2520. local core=_W(function()
  2521. local metatype = function(obj)
  2522.   local otype = type(obj)
  2523.   if otype == 'table' then
  2524.     local mt = getmetatable(obj)
  2525.     if mt and mt.__type then
  2526.       return mt.__type
  2527.     end
  2528.   end
  2529.   return otype
  2530. end
  2531.  
  2532. local failureMt = {
  2533.   __index = {},
  2534.   __tostring = function(e) return e.message end,
  2535.   __type = 'failure'
  2536. }
  2537.  
  2538. local pendingMt = {
  2539.   __index = {},
  2540.   __tostring = function(p) return p.message end,
  2541.   __type = 'pending'
  2542. }
  2543.  
  2544. local throw = error
  2545.  
  2546. return function()
  2547.   local mediator = mediator()
  2548.  
  2549.   local busted = {}
  2550.   busted.version = '2.0.rc3-0'
  2551.  
  2552.   local root = context()
  2553.   busted.context = root.ref()
  2554.  
  2555.   local environment = environment(busted.context)
  2556.  
  2557.   -- Kinda hacky sets
  2558.   environment.set("assert", luassert)
  2559.   environment.set("spy", spy)
  2560.   environment.set("stub", stub)
  2561.   environment.set("mock", mock)
  2562.  
  2563.   busted.executors = {}
  2564.   local executors = {}
  2565.  
  2566.   busted.status = status
  2567.  
  2568.   busted.getTrace = function(element, level, msg)
  2569.     level = level or 3
  2570.  
  2571.     -- Find first non-current program trace
  2572.     local _, info = pcall(error, "", level)
  2573.     local current = '^' .. shell.getRunningProgram() .. ':'
  2574.     while info:match(current) or info:match('pcall') do
  2575.       level = level + 1
  2576.       local _, info = pcall(error, "", level)
  2577.     end
  2578.  
  2579.     level = level + 1
  2580.  
  2581.     local traceback = ""
  2582.     for i = level, level+20 do
  2583.       local _, info = pcall(error, "", i)
  2584.       local info = info:match("[^:]+:[0-9]+")
  2585.       if not info then break end
  2586.       traceback = traceback .. "\n  " .. info
  2587.     end
  2588.  
  2589.     local _, info = pcall(error, "", level)
  2590.     local source, line = info:match("([^:]+):([0-9]+)")
  2591.  
  2592.     local info = {
  2593.       traceback = traceback:sub(2), -- Remove first \n
  2594.       message = msg,
  2595.       source = source,
  2596.       short_src = source,
  2597.       currentline = line or "<unknown>",
  2598.     }
  2599.  
  2600.     local file = busted.getFile(element)
  2601.     return file.getTrace(file.name, info)
  2602.   end
  2603.  
  2604.   busted.rewriteMessage = function(element, message)
  2605.     local file = busted.getFile(element)
  2606.  
  2607.     return file.rewriteMessage and file.rewriteMessage(file.name, message) or message
  2608.   end
  2609.  
  2610.   function busted.publish(...)
  2611.     return mediator:publish(...)
  2612.   end
  2613.  
  2614.   function busted.subscribe(...)
  2615.     return mediator:subscribe(...)
  2616.   end
  2617.  
  2618.   function busted.getFile(element)
  2619.     local current, parent = element, busted.context.parent(element)
  2620.  
  2621.     while parent do
  2622.       if parent.file then
  2623.         local file = parent.file[1]
  2624.         return {
  2625.           name = file.name,
  2626.           getTrace = file.run.getTrace,
  2627.           rewriteMessage = file.run.rewriteMessage
  2628.         }
  2629.       end
  2630.  
  2631.       if parent.descriptor == 'file' then
  2632.         return {
  2633.           name = parent.name,
  2634.           getTrace = parent.run.getTrace,
  2635.           rewriteMessage = parent.run.rewriteMessage
  2636.         }
  2637.       end
  2638.  
  2639.       parent = busted.context.parent(parent)
  2640.     end
  2641.  
  2642.     return parent
  2643.   end
  2644.  
  2645.   busted.fail = throw
  2646.  
  2647.   function busted.pending(msg)
  2648.     local p = { message = msg }
  2649.     setmetatable(p, pendingMt)
  2650.     throw(p)
  2651.   end
  2652.  
  2653.   function busted.replaceErrorWithFail(callable)
  2654.     local env = {}
  2655.     local f = getmetatable(callable).__call or callable
  2656.     setmetatable(env, { __index = getfenv(f) })
  2657.     env.error = busted.fail
  2658.     setfenv(f, env)
  2659.   end
  2660.  
  2661.   function busted.wrapEnv(callable)
  2662.     if (type(callable) == 'function' or getmetatable(callable).__call) then
  2663.       -- prioritize __call if it exists, like in files
  2664.       environment.wrap(getmetatable(callable).__call or callable)
  2665.     end
  2666.   end
  2667.  
  2668.   function busted.safe(descriptor, run, element)
  2669.     busted.context.push(element)
  2670.     local trace, message
  2671.     local status = 'success'
  2672.  
  2673.     local ret = { xpcall(run, function(msg)
  2674.       local errType = metatype(msg)
  2675.       status = (errType == 'string' and 'error' or errType)
  2676.       message = busted.rewriteMessage(element, tostring(msg))
  2677.       trace = busted.getTrace(element, 3, msg)
  2678.     end) }
  2679.  
  2680.     if not ret[1] then
  2681.       busted.publish({ status, descriptor }, element, busted.context.parent(element), message, trace)
  2682.     end
  2683.     ret[1] = busted.status(status)
  2684.  
  2685.     busted.context.pop()
  2686.     return unpack(ret)
  2687.   end
  2688.  
  2689.   function busted.register(descriptor, executor)
  2690.     executors[descriptor] = executor
  2691.  
  2692.     local publisher = function(name, fn)
  2693.       if not fn and type(name) == 'function' then
  2694.         fn = name
  2695.         name = nil
  2696.       end
  2697.  
  2698.       local trace
  2699.  
  2700.       local ctx = busted.context.get()
  2701.       if busted.context.parent(ctx) then
  2702.         trace = busted.getTrace(ctx, 3, name)
  2703.       end
  2704.  
  2705.       busted.publish({ 'register', descriptor }, name, fn, trace)
  2706.     end
  2707.  
  2708.     busted.executors[descriptor] = publisher
  2709.     if descriptor ~= 'file' then
  2710.       environment.set(descriptor, publisher)
  2711.     end
  2712.  
  2713.     busted.subscribe({ 'register', descriptor }, function(name, fn, trace)
  2714.       local ctx = busted.context.get()
  2715.       local plugin = {
  2716.         descriptor = descriptor,
  2717.         name = name,
  2718.         run = fn,
  2719.         trace = trace
  2720.       }
  2721.  
  2722.       busted.context.attach(plugin)
  2723.  
  2724.       if not ctx[descriptor] then
  2725.         ctx[descriptor] = { plugin }
  2726.       else
  2727.         ctx[descriptor][#ctx[descriptor]+1] = plugin
  2728.       end
  2729.     end)
  2730.   end
  2731.  
  2732.   function busted.execute(current)
  2733.     if not current then current = busted.context.get() end
  2734.     for _, v in pairs(busted.context.children(current)) do
  2735.       local executor = executors[v.descriptor]
  2736.       if executor then
  2737.         busted.safe(v.descriptor, function() return executor(v) end, v)
  2738.       end
  2739.     end
  2740.   end
  2741.  
  2742.   return busted
  2743. end
  2744. end)
  2745. local init=_W(function()
  2746. math.randomseed(os.time())
  2747.  
  2748. local function shuffle(t, seed)
  2749.   if seed then math.randomseed(seed) end
  2750.   local n = #t
  2751.   while n >= 2 do
  2752.     local k = math.random(n)
  2753.     t[n], t[k] = t[k], t[n]
  2754.     n = n - 1
  2755.   end
  2756.   return t
  2757. end
  2758.  
  2759. return function(busted)
  2760.   local function remove(descriptors, element)
  2761.     for _, descriptor in ipairs(descriptors) do
  2762.       element.env[descriptor] = function(...)
  2763.         error("'" .. descriptor .. "' not supported inside current context block", 2)
  2764.       end
  2765.     end
  2766.   end
  2767.  
  2768.   local function exec(descriptor, element)
  2769.       if not element.env then element.env = {} end
  2770.  
  2771.       remove({ 'randomize' }, element)
  2772.       remove({ 'pending' }, element)
  2773.       remove({ 'describe', 'context', 'it', 'spec', 'test' }, element)
  2774.       remove({ 'setup', 'teardown', 'before_each', 'after_each' }, element)
  2775.  
  2776.       local ret = { busted.safe(descriptor, element.run, element) }
  2777.       return unpack(ret)
  2778.   end
  2779.  
  2780.   local function execAll(descriptor, current, propagate)
  2781.     local parent = busted.context.parent(current)
  2782.  
  2783.     if propagate and parent then
  2784.       local success, ancestor = execAll(descriptor, parent, propagate)
  2785.       if not success then
  2786.         return success, ancestor
  2787.       end
  2788.     end
  2789.  
  2790.     local list = current[descriptor] or {}
  2791.  
  2792.     local success = true
  2793.     for _, v in pairs(list) do
  2794.       if not exec(descriptor, v):success() then
  2795.         success = nil
  2796.       end
  2797.     end
  2798.     return success, current
  2799.   end
  2800.  
  2801.   local function dexecAll(descriptor, current, propagate)
  2802.     local parent = busted.context.parent(current)
  2803.     local list = current[descriptor] or {}
  2804.  
  2805.     local success = true
  2806.     for _, v in pairs(list) do
  2807.       if not exec(descriptor, v):success() then
  2808.         success = nil
  2809.       end
  2810.     end
  2811.  
  2812.     if propagate and parent then
  2813.       if not dexecAll(descriptor, parent, propagate) then
  2814.         success = nil
  2815.       end
  2816.     end
  2817.     return success
  2818.   end
  2819.  
  2820.   local file = function(file)
  2821.     busted.publish({ 'file', 'start' }, file)
  2822.  
  2823.     busted.wrapEnv(file.run)
  2824.     if not file.env then file.env = {} end
  2825.  
  2826.     local randomize = busted.randomize
  2827.     file.env.randomize = function() randomize = true end
  2828.  
  2829.     if busted.safe('file', file.run, file):success() then
  2830.       if randomize then
  2831.         file.randomseed = busted.randomseed
  2832.         shuffle(busted.context.children(file), busted.randomseed)
  2833.       end
  2834.       if execAll('setup', file) then
  2835.         busted.execute(file)
  2836.       end
  2837.       dexecAll('teardown', file)
  2838.     end
  2839.  
  2840.     busted.publish({ 'file', 'end' }, file.name)
  2841.   end
  2842.  
  2843.   local describe = function(describe)
  2844.     local parent = busted.context.parent(describe)
  2845.  
  2846.     busted.publish({ 'describe', 'start' }, describe, parent)
  2847.  
  2848.     if not describe.env then describe.env = {} end
  2849.  
  2850.     local randomize = busted.randomize
  2851.     describe.env.randomize = function() randomize = true end
  2852.  
  2853.     if busted.safe('describe', describe.run, describe):success() then
  2854.       if randomize then
  2855.         describe.randomseed = busted.randomseed
  2856.         shuffle(busted.context.children(describe), busted.randomseed)
  2857.       end
  2858.       if execAll('setup', describe) then
  2859.         busted.execute(describe)
  2860.       end
  2861.       dexecAll('teardown', describe)
  2862.     end
  2863.  
  2864.     busted.publish({ 'describe', 'end' }, describe, parent)
  2865.   end
  2866.  
  2867.   local it = function(element)
  2868.     local finally
  2869.  
  2870.     busted.publish({ 'test', 'start' }, element, parent)
  2871.  
  2872.     if not element.env then element.env = {} end
  2873.  
  2874.     remove({ 'randomize' }, element)
  2875.     remove({ 'describe', 'context', 'it', 'spec', 'test' }, element)
  2876.     remove({ 'setup', 'teardown', 'before_each', 'after_each' }, element)
  2877.     element.env.finally = function(fn) finally = fn end
  2878.     element.env.pending = function(msg) busted.pending(msg) end
  2879.  
  2880.     local status = busted.status('success')
  2881.     local updateErrorStatus = function(descriptor)
  2882.       if element.message then element.message = element.message .. '\n' end
  2883.       element.message = (element.message or '') .. 'Error in ' .. descriptor
  2884.       status:update('error')
  2885.     end
  2886.  
  2887.     local parent = busted.context.parent(element)
  2888.     local pass, ancestor = execAll('before_each', parent, true)
  2889.  
  2890.     if pass then
  2891.       status:update(busted.safe('element', element.run, element))
  2892.     else
  2893.       updateErrorStatus('before_each')
  2894.     end
  2895.  
  2896.     if not element.env.done then
  2897.       remove({ 'pending' }, element)
  2898.       if finally then status:update(busted.safe('finally', finally, element)) end
  2899.       if not dexecAll('after_each', ancestor, true) then
  2900.         updateErrorStatus('after_each')
  2901.       end
  2902.  
  2903.       busted.publish({ 'test', 'end' }, element, parent, tostring(status))
  2904.     end
  2905.   end
  2906.  
  2907.   local pending = function(element)
  2908.     local parent = busted.context.parent(pending)
  2909.     busted.publish({ 'test', 'start' }, element, parent)
  2910.     busted.publish({ 'test', 'end' }, element, parent, 'pending')
  2911.   end
  2912.  
  2913.   busted.register('file', file)
  2914.  
  2915.   busted.register('describe', describe)
  2916.   busted.register('context', describe)
  2917.  
  2918.   busted.register('it', it)
  2919.   busted.register('spec', it)
  2920.   busted.register('test', it)
  2921.  
  2922.   busted.register('pending', pending)
  2923.  
  2924.   busted.register('setup')
  2925.   busted.register('teardown')
  2926.   busted.register('before_each')
  2927.   busted.register('after_each')
  2928.  
  2929.   busted.replaceErrorWithFail(assert)
  2930.   busted.replaceErrorWithFail(assert.True)
  2931.  
  2932.   return busted
  2933. end
  2934. end)
  2935. local api=_W(function()
  2936. local pairs,find = pairs,string.find
  2937.  
  2938. function loadConfig(path, default)
  2939.   local success, result = pcall(function() return loadfile(path)() end)
  2940.   if success then
  2941.     return configurationLoader()(result, default)
  2942.   else
  2943.     return false, false
  2944.   end
  2945. end
  2946.  
  2947. function loadOutput(busted, output, options, default)
  2948.   local outputHandler = outputHandlerLoader()(output, options, busted, default)
  2949.   outputHandler:subscribe(options)
  2950.   return outputHandler
  2951. end
  2952.  
  2953. function loadFiles(busted, loaders, rootFile, pattern)
  2954.   return testFileLoader(busted, loaders)(rootFile, pattern)
  2955. end
  2956.  
  2957. function execute(busted)
  2958.   busted.publish({ 'suite', 'start' })
  2959.   busted.execute()
  2960.   busted.publish({ 'suite', 'end' })
  2961. end
  2962.  
  2963. local hasTag = function(name, tag)
  2964.   return find(name, '#' .. tag) ~= nil
  2965. end
  2966.  
  2967. tags = {
  2968.   hasTag = hasTag,
  2969.  
  2970.   subscribeTags = function(busted, include, exclude)
  2971.     -- We report an error if the same tag appears in both `options.tags`
  2972.     -- and `options.excluded_tags` because it does not make sense for the
  2973.     -- user to tell Busted to include and exclude the same tests at the
  2974.     -- same time.
  2975.     for _, excluded in pairs(exclude) do
  2976.       for _, included in pairs(include) do
  2977.         if excluded == included then
  2978.           error('Cannot use --tags and --exclude-tags for the same tags')
  2979.         end
  2980.       end
  2981.     end
  2982.  
  2983.     local function checkTags(name)
  2984.       for _, tag in ipairs(exclude) do
  2985.         if hasTag(name, tag) then
  2986.           return nil, false
  2987.         end
  2988.       end
  2989.  
  2990.       for _, tag in ipairs(include) do
  2991.         if hasTag(name, tag) then
  2992.           return nil, true
  2993.         end
  2994.       end
  2995.  
  2996.       return nil, #include == 0
  2997.     end
  2998.  
  2999.     busted.subscribe({ 'register', 'describe' }, checkTags, { priority = 1 })
  3000.     busted.subscribe({ 'register', 'it' }, checkTags, { priority = 1 })
  3001.     busted.subscribe({ 'register', 'pending' }, checkTags, { priority = 1 })
  3002.   end
  3003. }
  3004.  
  3005. function run(options, default)
  3006.   local busted = core()
  3007.   init(busted)
  3008.  
  3009.   local path = options.cwd
  3010.   local errors, errorCount = {}, 0
  3011.  
  3012.   local config = loadConfig(fs.combine(path, '.busted'), options)
  3013.   if config then
  3014.     options = config
  3015.   elseif err then
  3016.     errors[#errors + 1] = err
  3017.     errors = errors + 1
  3018.   end
  3019.  
  3020.   -- Load test directory
  3021.   local rootFile = fs.combine(path, options.root)
  3022.  
  3023.   busted.subscribe({ 'error' }, function(element, parent, status)
  3024.     if element.descriptor == 'output' then
  3025.       errors[#errors + 1] = 'Cannot load output library: ' .. element.name
  3026.     end
  3027.     errorCount = errorCount + 1
  3028.     return nil, true
  3029.   end)
  3030.  
  3031.   busted.subscribe({ 'failure' }, function(element, parent, status)
  3032.     errorCount = errorCount + 1
  3033.     return nil, true
  3034.   end)
  3035.  
  3036.   -- Set up randomization options
  3037.   busted.randomize = options.randomize
  3038.   local randomseed = tonumber(options.seed)
  3039.   if randomseed then
  3040.     busted.randomseed = randomseed
  3041.   else
  3042.     errors[#errors + 1] = 'Argument to --seed must be a number'
  3043.     errorCount = errorCount + 1
  3044.  
  3045.     busted.randomseed = default.seed
  3046.   end
  3047.  
  3048.   -- Set up output handler to listen to events
  3049.   local outputHandlerOptions = {
  3050.     verbose = options.verbose,
  3051.     suppressPending = options['suppress-pending'],
  3052.     deferPrint = options['defer-print']
  3053.   }
  3054.  
  3055.   loadOutput(busted, options.output, outputHandlerOptions, default.output)
  3056.  
  3057.   local includeTags, excludeTags = options.tags, options['exclude-tags']
  3058.   if #includeTags > 0 or #excludeTags > 0 then
  3059.     tags.subscribeTags(busted, includeTags, excludeTags)
  3060.   end
  3061.  
  3062.   if options.env then
  3063.     busted.subscribe({ 'file', 'start' }, function(file)
  3064.       if not file.env then file.env = {} end
  3065.       for k, v in pairs(options.env) do
  3066.         file.env[k] = v
  3067.       end
  3068.     end)
  3069.   end
  3070.  
  3071.   local pattern = options.pattern
  3072.   local fileList = loadFiles(busted, options.loaders, rootFile, pattern)
  3073.   if #fileList == 0 then
  3074.     errors[#errors + 1] = 'No test files found matching Lua pattern: ' .. pattern
  3075.     errorCount = errorCount + 1
  3076.   end
  3077.  
  3078.   execute(busted)
  3079.  
  3080.   return errorCount, errors
  3081. end
  3082. end)
  3083. local plainTerminal=_W(function()
  3084. local s = say
  3085.  
  3086. return function(options, busted)
  3087.   local handler = outputHandlerBase(busted)
  3088.  
  3089.   local successDot =  '+'
  3090.   local failureDot =  '-'
  3091.   local errorDot =  '*'
  3092.   local pendingDot = '.'
  3093.  
  3094.   local pendingDescription = function(pending)
  3095.     local name = pending.name
  3096.  
  3097.     local string = s('output.pending') .. ' → ' ..
  3098.       pending.trace.short_src .. ' @ ' ..
  3099.       pending.trace.currentline  ..
  3100.       '\n' .. name
  3101.  
  3102.     if type(pending.message) == 'string' then
  3103.       string = string .. '\n' .. pending.message
  3104.     elseif pending.message ~= nil then
  3105.       string = string .. '\n' .. textutils.serialize(pending.message)
  3106.     end
  3107.  
  3108.     return string
  3109.   end
  3110.  
  3111.   local failureMessage = function(failure)
  3112.     local string
  3113.     if type(failure.message) == 'string' then
  3114.       string = failure.message
  3115.     elseif failure.message == nil then
  3116.       string = 'Nil error'
  3117.     else
  3118.       string = textutils.serialize(failure.message)
  3119.     end
  3120.  
  3121.     return string
  3122.   end
  3123.  
  3124.   local failureDescription = function(failure, isError)
  3125.     local string = s('output.failure') .. ' → '
  3126.     if isError then
  3127.       string = s('output.error') .. ' → '
  3128.     end
  3129.  
  3130.     if not failure.element.trace or not failure.element.trace.short_src then
  3131.       string = string ..
  3132.         failureMessage(failure) .. '\n' ..
  3133.         failure.name
  3134.     else
  3135.       string = string ..
  3136.         failure.element.trace.short_src .. ' @ ' ..
  3137.         failure.element.trace.currentline .. '\n' ..
  3138.         failure.name .. '\n' ..
  3139.         failureMessage(failure)
  3140.     end
  3141.  
  3142.     if options.verbose and failure.trace and failure.trace.traceback then
  3143.       string = string .. '\n' .. failure.trace.traceback
  3144.     end
  3145.  
  3146.     return string
  3147.   end
  3148.  
  3149.   local statusString = function()
  3150.     local successString = s('output.success_plural')
  3151.     local failureString = s('output.failure_plural')
  3152.     local pendingString = s('output.pending_plural')
  3153.     local errorString = s('output.error_plural')
  3154.  
  3155.     local ms = handler.getDuration()
  3156.     local successes = handler.successesCount
  3157.     local pendings = handler.pendingsCount
  3158.     local failures = handler.failuresCount
  3159.     local errors = handler.errorsCount
  3160.  
  3161.     if successes == 0 then
  3162.       successString = s('output.success_zero')
  3163.     elseif successes == 1 then
  3164.       successString = s('output.success_single')
  3165.     end
  3166.  
  3167.     if failures == 0 then
  3168.       failureString = s('output.failure_zero')
  3169.     elseif failures == 1 then
  3170.       failureString = s('output.failure_single')
  3171.     end
  3172.  
  3173.     if pendings == 0 then
  3174.       pendingString = s('output.pending_zero')
  3175.     elseif pendings == 1 then
  3176.       pendingString = s('output.pending_single')
  3177.     end
  3178.  
  3179.     if errors == 0 then
  3180.       errorString = s('output.error_zero')
  3181.     elseif errors == 1 then
  3182.       errorString = s('output.error_single')
  3183.     end
  3184.  
  3185.     local formattedTime = ('%.6f'):format(ms):gsub('([0-9])0+$', '%1')
  3186.  
  3187.     return successes .. ' ' .. successString .. ' / ' ..
  3188.       failures .. ' ' .. failureString .. ' / ' ..
  3189.       errors .. ' ' .. errorString .. ' / ' ..
  3190.       pendings .. ' ' .. pendingString .. ' : ' ..
  3191.       formattedTime .. ' ' .. s('output.seconds')
  3192.   end
  3193.  
  3194.   handler.testEnd = function(element, parent, status, debug)
  3195.     if not options.deferPrint then
  3196.       local string = successDot
  3197.  
  3198.       if status == 'pending' then
  3199.         string = pendingDot
  3200.       elseif status == 'failure' then
  3201.         string = failureDot
  3202.       elseif status == 'error' then
  3203.         string = errorDot
  3204.       end
  3205.  
  3206.       write(string)
  3207.     end
  3208.  
  3209.     return nil, true
  3210.   end
  3211.  
  3212.   handler.suiteEnd = function(name, parent)
  3213.     print('')
  3214.     print(statusString())
  3215.  
  3216.     for i, pending in pairs(handler.pendings) do
  3217.       print('')
  3218.       print(pendingDescription(pending))
  3219.     end
  3220.  
  3221.     for i, err in pairs(handler.failures) do
  3222.       print('')
  3223.       print(failureDescription(err))
  3224.     end
  3225.  
  3226.     for i, err in pairs(handler.errors) do
  3227.       print('')
  3228.       print(failureDescription(err, true))
  3229.     end
  3230.  
  3231.     return nil, true
  3232.   end
  3233.  
  3234.   handler.error = function(element, parent, message, debug)
  3235.     write(errorDot)
  3236.  
  3237.     return nil, true
  3238.   end
  3239.  
  3240.   busted.subscribe({ 'test', 'end' }, handler.testEnd, { predicate = handler.cancelOnPending })
  3241.   busted.subscribe({ 'suite', 'end' }, handler.suiteEnd)
  3242.   busted.subscribe({ 'error', 'file' }, handler.error)
  3243.   busted.subscribe({ 'error', 'describe' }, handler.error)
  3244.  
  3245.   return handler
  3246. end
  3247. end)
  3248. local done=_W(function()
  3249. local M = {}
  3250.  
  3251. -- adds tokens to the current wait list, does not change order/unordered
  3252. M.wait = function(self, ...)
  3253.   local tlist = { ... }
  3254.  
  3255.   for _, token in ipairs(tlist) do
  3256.     if type(token) ~= 'string' then
  3257.       error('Wait tokens must be strings. Got '..type(token), 2)
  3258.     end
  3259.     table.insert(self.tokens, token)
  3260.   end
  3261. end
  3262.  
  3263. -- set list as unordered, adds tokens to current wait list
  3264. M.wait_unordered = function(self, ...)
  3265.   self.ordered = false
  3266.   self:wait(...)
  3267. end
  3268.  
  3269. -- set list as ordered, adds tokens to current wait list
  3270. M.wait_ordered = function(self, ...)
  3271.   self.ordered = true
  3272.   self:wait(...)
  3273. end
  3274.  
  3275. -- generates a message listing tokens received/open
  3276. M.tokenlist = function(self)
  3277.   local list
  3278.  
  3279.   if #self.tokens_done == 0 then
  3280.     list = 'No tokens received.'
  3281.   else
  3282.     list = 'Tokens received ('..tostring(#self.tokens_done)..')'
  3283.     local s = ': '
  3284.  
  3285.     for _,t in ipairs(self.tokens_done) do
  3286.       list = list .. s .. '\''..t..'\''
  3287.       s = ', '
  3288.     end
  3289.  
  3290.     list = list .. '.'
  3291.   end
  3292.  
  3293.   if #self.tokens == 0 then
  3294.     list = list .. ' No more tokens expected.'
  3295.   else
  3296.     list = list .. ' Tokens not received ('..tostring(#self.tokens)..')'
  3297.     local s = ': '
  3298.  
  3299.     for _, t in ipairs(self.tokens) do
  3300.       list = list .. s .. '\''..t..'\''
  3301.       s = ', '
  3302.     end
  3303.  
  3304.     list = list .. '.'
  3305.   end
  3306.  
  3307.   return list
  3308. end
  3309.  
  3310. -- marks a token as completed, checks for ordered/unordered, checks for completeness
  3311. M.done = function(self, ...) self:_done(...) end  -- extra wrapper for same error level constant as __call method
  3312. M._done = function(self, token)
  3313.   if token then
  3314.     if type(token) ~= 'string' then
  3315.       error('Wait tokens must be strings. Got '..type(token), 3)
  3316.     end
  3317.  
  3318.     if self.ordered then
  3319.       if self.tokens[1] == token then
  3320.         table.remove(self.tokens, 1)
  3321.         table.insert(self.tokens_done, token)
  3322.       else
  3323.         if self.tokens[1] then
  3324.           error(('Bad token, expected \'%s\' got \'%s\'. %s'):format(self.tokens[1], token, self:tokenlist()), 3)
  3325.         else
  3326.           error(('Bad token (no more tokens expected) got \'%s\'. %s'):format(token, self:tokenlist()), 3)
  3327.         end
  3328.       end
  3329.     else
  3330.       -- unordered
  3331.       for i, t in ipairs(self.tokens) do
  3332.         if t == token then
  3333.           table.remove(self.tokens, i)
  3334.           table.insert(self.tokens_done, token)
  3335.           token = nil
  3336.           break
  3337.         end
  3338.       end
  3339.  
  3340.       if token then
  3341.         error(('Unknown token \'%s\'. %s'):format(token, self:tokenlist()), 3)
  3342.       end
  3343.     end
  3344.   end
  3345.   if not next(self.tokens) then
  3346.     -- no more tokens, so we're really done...
  3347.     self.done_cb()
  3348.   end
  3349. end
  3350.  
  3351.  
  3352. -- wraps a done callback into a done-object supporting tokens to sign-off
  3353. M.new = function(done_callback)
  3354.   local obj = {
  3355.     tokens = {},
  3356.     tokens_done = {},
  3357.     done_cb = done_callback,
  3358.     ordered = true,  -- default for sign off of tokens
  3359.   }
  3360.  
  3361.   return setmetatable( obj, {
  3362.     __call = function(self, ...)
  3363.       self:_done(...)
  3364.     end,
  3365.     __index = M,
  3366.   })
  3367. end
  3368.  
  3369. return M
  3370. end)
  3371. return {say=say, cliargs=cliargs, outputHandlerBase=outputHandlerBase, ansicolors=ansicolors, colorTerminal=colorTerminal, state=state, assert=assert, util=util, luassert=luassert, outputHandlerLoader=outputHandlerLoader, environment=environment, spy=spy, stub=stub, configurationLoader=configurationLoader, fileType_Lua=fileType_Lua, testFileLoader=testFileLoader, context=context, mediator=mediator, status=status, mock=mock, core=core, init=init, api=api, plainTerminal=plainTerminal, done=done, }
Advertisement
Add Comment
Please, Sign In to add comment