# General Lua Libraries for Lua 5.1, 5.2 & 5.3
# Copyright (C) 2011-2018 stdlib authors

before: |
  this_module = 'std'
  global_table = '_G'

  exported_apis = {'assert', 'elems', 'eval', 'getmetamethod',
                   'ielems', 'ipairs', 'npairs', 'pairs',
                   'require', 'ripairs', 'rnpairs'}

  -- Tables with iterator metamethods used by various examples.
  __pairs = setmetatable({content='a string'}, {
     __pairs = function(t)
        return function(x, n)
           if n < #x.content then
              return n+1, string.sub(x.content, n+1, n+1)
           end
        end, t, 0
     end,
  })
  __index = setmetatable({content='a string'}, {
     __index = function(t, n)
        if n <= #t.content then
           return t.content:sub(n, n)
        end
     end,
     __len = function(t)
        return #t.content
     end,
  })

  M = require(this_module)
  M.version = nil               -- previous specs may have autoloaded it


specify std:
- context when required:
  - it does not touch the global table:
      expect(show_apis {added_to=global_table, by=this_module}).
         to_equal {}
  - it exports the documented apis:
      t = {}
      for k in pairs(M) do
         t[#t + 1] = k
      end
      expect(t).to_contain.a_permutation_of(exported_apis)

- context when lazy loading:
  - it has no submodules on initial load:
      for _, v in pairs(M) do
         expect(type(v)).not_to_be 'table'
      end
  - it loads submodules on demand:
      lazy = M.math
      expect(lazy).to_be(require 'std.math')
  - it loads submodule functions on demand:
      expect(M.math.round(3.141592)).to_be(3)

- describe assert:
  - before:
      f = M.assert

  - context with bad arguments:
      badargs.diagnose(f, 'std.assert(?any, ?string, ?any*)')

  - context when it does not trigger:
    - it has a truthy initial argument:
        expect(f(1)).not_to_raise 'any error'
        expect(f(true)).not_to_raise 'any error'
        expect(f 'yes').not_to_raise 'any error'
        expect(f(false == false)).not_to_raise 'any error'
    - it returns the initial argument:
        expect(f(1)).to_be(1)
        expect(f(true)).to_be(true)
        expect(f 'yes').to_be 'yes'
        expect(f(false == false)).to_be(true)
  - context when it triggers:
    - it has a falsey initial argument:
        expect(f()).to_raise()
        expect(f(false)).to_raise()
        expect(f(1 == 0)).to_raise()
    - it throws an optional error string:
        expect(f(false, 'ah boo')).to_raise 'ah boo'
    - it plugs specifiers with string.format: |
        expect(f(nil, '%s %d: %q', 'here', 42, 'a string')).
           to_raise(string.format('%s %d: %q', 'here', 42, 'a string'))


- describe elems:
  - before:
      f = M.elems

  - context with bad arguments:
      badargs.diagnose(f, 'std.elems(table)')

  - it is an iterator over table values:
      t = {}
      for e in f {'foo', bar='baz', 42} do
         t[#t + 1] = e
      end
      expect(t).to_contain.a_permutation_of {'foo', 'baz', 42}
  - it respects __pairs metamethod: |
      t = {}
      for v in f(__pairs) do
         t[#t + 1] = v
      end
      expect(t).
         to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
  - it works for an empty list:
      t = {}
      for e in f {} do
         t[#t + 1] = e
      end
      expect(t).to_equal {}


- describe eval:
  - before:
      f = M.eval

  - context with bad arguments:
      badargs.diagnose(f, 'std.eval(string)')

  - it diagnoses invalid lua:
      # Some internal error when eval tries to call uncompilable '=' code.
      expect(f '=').to_raise()
  - it evaluates a string of lua code:
      expect(f 'math.min(2, 10)').to_be(math.min(2, 10))


- describe getmetamethod:
  - before:
      f = M.getmetamethod

  - context with bad arguments:
      badargs.diagnose(f, 'std.getmetamethod(?any, string)')

  - context with a table:
    - before:
        method = function()
           return 'called'
        end
        functor = setmetatable({}, {__call=method})
        t = setmetatable({}, {
           _type='table', _method=method, _functor=functor,
        })
    - it returns nil for missing metamethods:
        expect(f(t, 'not a metamethod on t')).to_be(nil)
    - it returns nil for non-callable metatable entries:
        expect(f(t, '_type')).to_be(nil)
    - it returns a method from the metatable:
        expect(f(t, '_method')).to_be(method)
        expect(f(t, '_method')()).to_be 'called'
    - it returns a functor from the metatable:
        expect(f(t, '_functor')).to_be(functor)
        expect(f(t, '_functor')()).to_be 'called'


- describe ielems:
  - before:
      f = M.ielems

  - context with bad arguments:
      badargs.diagnose(f, 'std.ielems(table)')

  - it is an iterator over integer-keyed table values:
      t = {}
      for e in f {'foo', 42} do
         t[#t + 1] = e
      end
      expect(t).to_equal {'foo', 42}
  - it ignores the dictionary part of a table:
      t = {}
      for e in f {'foo', 42; bar='baz', qux='quux'} do
         t[#t + 1] = e
      end
      expect(t).to_equal {'foo', 42}
  - it respects __len metamethod:
      t = {}
      for v in f(__index) do
         t[#t + 1] = v
      end
      expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
  - it works for an empty list:
      t = {}
      for e in f {} do
         t[#t + 1] = e
      end
      expect(t).to_equal {}


- describe ipairs:
  - before:
      f = M.ipairs

  - context with bad arguments:
      badargs.diagnose(f, 'std.ipairs(table)')

  - it is an iterator over integer-keyed table values:
      t = {}
      for i, v in f {'foo', 42} do
         t[i] = v
      end
      expect(t).to_equal {'foo', 42}
  - it ignores the dictionary part of a table:
      t = {}
      for i, v in f {'foo', 42; bar='baz', qux='quux'} do
         t[i] = v
      end
      expect(t).to_equal {'foo', 42}
  - it respects __len metamethod:
      t = {}
      for k, v in f(__index) do
         t[k] = v
      end
      expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
  - it works for an empty list:
      t = {}
      for i, v in f {} do
         t[i] = v
      end
      expect(t).to_equal {}


- describe npairs:
  - before:
      f = M.npairs

  - context with bad arguments:
      badargs.diagnose(f, 'std.npairs(table)')

  - it is an iterator over integer-keyed table values:
      t = {}
      for i, v in f {'foo', 42, nil, nil, 'five'} do
         t[i] = v
      end
      expect(t).to_equal {'foo', 42, nil, nil, 'five'}
  - it ignores the dictionary part of a table:
      t = {}
      for i, v in f {'foo', 42, nil, nil, 'five'; bar='baz', qux='quux'} do
         t[i] = v
      end
      expect(t).to_equal {'foo', 42, nil, nil, 'five'}
  - it respects __len metamethod:
      t = {}
      for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do
         t[#t + 1] = tostring(v)
      end
      expect(table.concat(t, ',')).to_be 'nil,false,nil,nil'
  - it works for an empty list:
      t = {}
      for i, v in f {} do
         t[i] = v
      end
      expect(t).to_equal {}


- describe pairs:
  - before:
      f = M.pairs

  - context with bad arguments:
      badargs.diagnose(f, 'std.pairs(table)')

  - it is an iterator over all table values:
      t = {}
      for k, v in f {'foo', bar='baz', 42} do
         t[k] = v
      end
      expect(t).to_equal {'foo', bar='baz', 42}
  - it respects __pairs metamethod: |
      t = {}
      for k, v in f(__pairs) do
         t[k] = v
      end
      expect(t).
         to_contain.a_permutation_of {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
  - it works for an empty list:
      t = {}
      for k, v in f {} do
         t[k] = v
      end
      expect(t).to_equal {}


- describe require:
  - before:
      f = M.require

  - context with bad arguments:
      badargs.diagnose(f, 'std.require(string, ?string, ?string, ?string)')

  - it diagnoses non-existent module:
      expect(f('module-not-exists', '', '')).to_raise 'module-not-exists'
  - it diagnoses module too old:
      expect(f('std', '9999', '9999')).
         to_raise "require 'std' with at least version 9999,"
  - it diagnoses module too new:
      expect(f('std', '0', '0')).
         to_raise "require 'std' with version less than 0,"
  - context when the module version is compatible:
    - it returns the module table:
        expect(f('std', '0', '9999')).to_be(require 'std')
    - it places no upper bound by default:
        expect(f('std', '0')).to_be(require 'std')
    - it places no lower bound by default:
        expect(f 'std').to_be(require 'std')
    - it uses _VERSION when version field is nil:
        expect(luaproc [[
          package.loaded['poop'] = {_VERSION='41.1'}
          f = require 'std'.require
          print(f('poop', '41', '9999')._VERSION)
        ]]).to_succeed_with '41.1\n'
  - context with semantic versioning:
    - before:
        std = require 'std'
        ver = std.version
        std.version = '1.2.3'
    - after:
        std.version = ver
    - it diagnoses module too old:
        expect(f('std', '1.2.4')).
           to_raise "require 'std' with at least version 1.2.4,"
        expect(f('std', '1.3')).
           to_raise "require 'std' with at least version 1.3,"
        expect(f('std', '2.1.2')).
           to_raise "require 'std' with at least version 2.1.2,"
        expect(f('std', '2')).
           to_raise "require 'std' with at least version 2,"
        expect(f('std', '1.2.10')).
           to_raise "require 'std' with at least version 1.2.10,"
    - it diagnoses module too new:
        expect(f('std', nil, '1.2.2')).
           to_raise "require 'std' with version less than 1.2.2,"
        expect(f('std', nil, '1.1')).
           to_raise "require 'std' with version less than 1.1,"
        expect(f('std', nil, '1.1.2')).
           to_raise "require 'std' with version less than 1.1.2,"
        expect(f('std', nil, '1')).
           to_raise "require 'std' with version less than 1,"
    - it returns modules with version in range:
        expect(f('std')).to_be(std)
        expect(f('std', '1')).to_be(std)
        expect(f('std', '1.2.3')).to_be(std)
        expect(f('std', nil, '2')).to_be(std)
        expect(f('std', nil, '1.3')).to_be(std)
        expect(f('std', nil, '1.2.10')).to_be(std)
        expect(f('std', '1.2.3', '1.2.4')).to_be(std)
  - context with several numbers in version string:
    - before:
        std = require 'std'
        ver = std.version
        std.version = 'standard library for Lua 5.3 / 41.0.0'
    - after:
        std.version = ver
    - it diagnoses module too old:
        expect(f('std', '42')).to_raise()
    - it diagnoses module too new:
        expect(f('std', nil, '40')).to_raise()
    - it returns modules with version in range:
        expect(f('std')).to_be(std)
        expect(f('std', '1')).to_be(std)
        expect(f('std', '41')).to_be(std)
        expect(f('std', nil, '42')).to_be(std)
        expect(f('std', '41', '42')).to_be(std)


- describe ripairs:
  - before:
      f = M.ripairs

  - context with bad arguments:
      badargs.diagnose(f, 'std.ripairs(table)')

  - it returns a function, the table and a number:
      fn, t, i = f {1, 2, 3}
      expect({type(fn), t, type(i)}).to_equal {'function', {1, 2, 3}, 'number'}
  - it iterates over the array part of a table:
      t, u = {1, 2, 3; a=4, b=5, c=6}, {}
      for i, v in f(t) do
         u[i] = v
      end
      expect(u).to_equal {1, 2, 3}
  - it returns elements in reverse order:
      t, u = {'one', 'two', 'five'}, {}
      for _, v in f(t) do
         u[#u + 1] = v
      end
      expect(u).to_equal {'five', 'two', 'one'}
  - it respects __len metamethod:
      t = {}
      for i, v in f(__index) do
         t[i] = v
      end
      expect(t).to_equal {'a', ' ', 's', 't', 'r', 'i', 'n', 'g'}
      t = {}
      for _, v in f(__index) do
         t[#t + 1] = v
      end
      expect(t).to_equal {'g', 'n', 'i', 'r', 't', 's', ' ', 'a'}
  - it works with the empty list:
      t = {}
      for k, v in f {} do
         t[k] = v
      end
      expect(t).to_equal {}


- describe rnpairs:
  - before:
      f = M.rnpairs

  - context with bad arguments:
      badargs.diagnose(f, 'std.rnpairs(table)')

  - it returns a function, the table and a number:
      fn, t, i = f {1, 2, nil, nil, 3}
      expect({type(fn), t, type(i)}).
         to_equal {'function', {1, 2, nil, nil, 3}, 'number'}
  - it iterates over the array part of a table:
      t, u = {1, 2, nil, nil, 3; a=4, b=5, c=6}, {}
      for i, v in f(t) do
         u[i] = v
      end
      expect(u).to_equal {1, 2, nil, nil, 3}
  - it returns elements in reverse order:
      t, u, i = {'one', 'two', nil, nil, 'five'}, {}, 1
      for _, v in f(t) do
         u[i], i = v, i + 1
      end
      expect(u).to_equal {'five', nil, nil, 'two', 'one'}
  - it respects __len metamethod:
      t = {}
      for _, v in f(setmetatable({[2]=false}, {__len=function(self) return 4 end})) do
         t[#t + 1] = tostring(v)
      end
      expect(table.concat(t, ',')).to_be 'nil,nil,false,nil'
  - it works with the empty list:
      t = {}
      for k, v in f {} do
         t[k] = v
      end
      expect(t).to_equal {}