-- Penlight 1.11.0-1 | /lua/pl/data.lua | https://github.com/lunarmodules/Penlight | License: MIT | Minified using https://www.npmjs.com/package/luamin/v/1.0.4
-- Copyright (C) 2009-2016 Steve Donovan, David Manura.
-- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
-- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
local a=require'pl.utils'local b=rawget(_G,'_DEBUG')local c,d,e,f=a.patterns,a.function_arg,a.split,a.array_tostring;local g,h=table.insert,table.concat;local i=string.gsub;local io=io;local _G,print,type,tonumber,ipairs,setmetatable=_G,print,type,tonumber,ipairs,setmetatable;local j={}local k;local function l(m)return m:gsub('%s+$','')end;local function n(m)return l(m):gsub('^%s*','')end;local function o(p)return setmetatable(p,require('pl.List'))end;local function q(r,s)local t={}for u=1,#s do t[u]=r(s[u])end;return t end;local function v(w,x,y,z)local A;if y and w:match'"'then w=w:gsub('"([^"]+)"',function(B)local m,C=B:gsub(',','\001')if C>0 then A=true end;return m end)if A then A=function(m)return m:gsub('\001',',')end end end;local t=e(w,x,false,z)if y then if A then t=q(A,t)end;if w:match',$'then g(t,'')end end;return o(t)end;local function D(s,E)for u=1,#s do if E==s[u]then return u end end end;local F={column_by_name=function(self,G)if type(G)=='number'then G='$'..G end;local H={}for t in j.query(self,G)do g(H,t)end;return o(H)end,copy_select=function(self,I)I=k(I,self)local J=j.query(self,I)local t={}local K=o{J()}while#K>0 do g(t,K)K=o{J()}end;t.delim=self.delim;return j.new(t,v(I.fields,','))end,column_names=function(self)return self.fieldnames end}local L;F.__index=function(self,G)local M=F[G]if M then return M end;if not L then L=require'pl.array2d'end;return L[G]end;local N={',','\t',' ',';'}local function O(w)if w==''then return' 'end;for P,x in ipairs(N)do if w:find(x)then return x==' 'and'%s+'or x end end;return' 'end;local function Q(M,R)local S,T;local U=R=='r'if type(M)=='string'then if M=='stdin'then M=io.stdin elseif M=='stdout'then M=io.stdout else M,T=io.open(M,R)if not M then return nil,T end;S=true end end;if M and(U and not M.read or not U and not M.write)then return nil,"not a file-like object"end;return M,nil,S end;function j.read(V,W)local X,w;local Y={}if not W then W={}end;local M,T,S=Q(V,'r')if not M then return nil,T end;local Z=W.thousands_dot;local y=W.csv;if y then W.delim=','end;local tonumber=tonumber;local function _(a0)if Z then a0=a0:gsub('%.(...)','%1')end;if y and a0==''then a0='0'end;local E=tonumber(a0)if E==nil then return nil,"not a number"end;return E end;X=1;w=M:read()if not w then return nil,"empty file"end;Y.delim=W.delim and W.delim or O(w)local x=Y.delim;local a1;local a2={}local function a3(a4,a5)a1=a1 or{}g(a2,a4)g(a1,a5)end;if W.numfields then for P,z in ipairs(W.numfields)do a3(z,_)end end;local a6;if x=='%s+'and w:find(x)==1 then a6=function(m)return m:gsub('^%s+','')end;w=a6(w)end;if not W.fieldnames then local a7,a8;a7=v(w,x,y)if not W.convert then a8=q(tonumber,a7)if#a8==#a7 then g(Y,a8)for u=1,#a8 do a3(u,_)end else a8=nil end else for a4,a5 in pairs(W.convert)do a3(a4,a5)end end;if a8==nil then W.fieldnames=a7 end;w=M:read()X=X+1;if a6 then w=a6(w)end elseif type(W.fieldnames)=='string'then W.fieldnames=v(W.fieldnames,x,y)end;local a9;if W.fieldnames then Y.fieldnames=W.fieldnames;if W.last_field_collect then a9=#Y.fieldnames end;if not W.no_convert then local a7=v(w,Y.delim,y,a9)for u=1,#a7 do if not D(a2,u)and _(a7[u])then a3(u,_)end end end end;while w do if not w:find('^%s*$')then if a6 then w=a6(w)end;local a7=v(w,x,y,a9)if a1 then for aa=1,#a2 do local u,a5=a2[aa],a1[aa]local ab,T=a5(a7[u])if ab==nil then return nil,T..": "..a7[u].." at line "..X else a7[u]=ab end end end;g(Y,a7)end;w=M:read()X=X+1 end;if S then M:close()end;if x=='%s+'then Y.delim=' 'end;if not Y.fieldnames then Y.fieldnames={}end;return j.new(Y)end;local function ac(j,M,K,x)j.temp=f(K,j.temp)M:write(h(j.temp,x),'\n')end;function F:write_row(M,K)ac(self,M,K,self.delim)end;function j.write(j,V,ad,x)local M,T,S=Q(V,'w')if not M then return nil,T end;if not ad then ad=j.fieldnames end;x=x or'\t'if ad and#ad>0 then M:write(h(ad,x),'\n')end;for u=1,#j do ac(j,M,j[u],x)end;if S then M:close()end;return true end;function F:write(V)j.write(self,V,self.fieldnames,self.delim)end;local function ae(a7,af)for u=1,#a7 do local M=n(a7[u])af[u]=M;a7[u]=M:gsub('%W','_')end end;function j.new(ag,ad)ag.fieldnames=ag.fieldnames or ad or''if not ag.delim and type(ag.fieldnames)=='string'then ag.delim=O(ag.fieldnames)ag.fieldnames=v(ag.fieldnames,ag.delim)end;ag.fieldnames=o(ag.fieldnames)ag.original_fieldnames={}ae(ag.fieldnames,ag.original_fieldnames)setmetatable(ag,F)return ag end;local ah=[[
return function (t)
    local i = 0
    local v
    local ls = {}
    for i,v in ipairs(t) do
        if CONDITION then
            ls[#ls+1] = v
        end
    end
    table.sort(ls,function(v1,v2)
        return SORT_EXPR
    end)
    local n = #ls
    return function()
        i = i + 1
        v = ls[i]
        if i > n then return end
        return FIELDLIST
    end
end
]]local ai=[[
return function (t)
    local n = #t
    local i = 0
    local v
    return function()
        repeat
            i = i + 1
            v = t[i]
        until i > n or CONDITION
        if i > n then return end
        return FIELDLIST
    end
end
]]local function aj(m)return type(m)=='string'end;local ak;local function al(j)return h(j.fieldnames,',')end;local function am(j,M)local a4;if M:find'^%d+$'then a4=tonumber(M)else a4=D(j.fieldnames,M)end;if a4 then return'v['..a4 ..']'else ak=M..' not found in '..al(j)return M end end;local function an(j,ao)ak=nil;local a7=ao.fields;local a2=a7:find'%$'or#j.fieldnames==0;if a7:find'^%s*%*%s*'then if not a2 then a7=al(j)else local ap=#j[1]a7={}for u=1,ap do g(a7,'$'..u)end;a7=h(a7,',')end end;local aq=c.IDEN;if a2 then aq='%$(%d+)'else a7=l(a7):gsub('[^,%w]','_')end;local am=a.bind1(am,j)local ar=i(a7,aq,am)if ak then return nil,ak end;ao.fields=a7;ao.proc_fields=ar;ao.where=ao.where or'true'if aj(ao.where)then ao.where=i(ao.where,aq,am)ak=nil end;return true end;k=function(m,j)local as;local ao={}local at,au=m:find('where ')local av,aw=m:find('sort by ')if at then as=(av or 0)-1;ao.where=m:sub(au+1,as)end;if av then ao.sort_by=m:sub(aw+1)end;as=(at or av or 0)-1;ao.fields=m:sub(1,as)local ax,T=an(j,ao)if not ax then return nil,T else return ao end end;function j.query(j,I,ay,az)local T;if aj(I)then I,T=k(I,j)if not I then return nil,T end elseif type(I)=='table'then if type(I.fields)=='table'then I.fields=h(I.fields,',')end;if not I.proc_fields then local ax,T=an(j,I)if not ax then return nil,T end end else return nil,"condition must be a string or a table"end;local aA;if I.sort_by then aA=ah else aA=ai end;local a7=I.proc_fields or I.fields;if az then a7='{'..a7 ..'}'end;aA=aA:gsub('FIELDLIST',a7)if aj(I.where)then aA=aA:gsub('CONDITION',I.where)I.where=nil else aA=aA:gsub('CONDITION','_condn(v)')I.where=d(0,I.where,'condition.where must be callable')end;if I.sort_by then local aB,aC,aD;local aE=I.sort_by;local aF,aG=aE:find('%s+')if aF then aC,aD=aE:sub(1,aF-1),aE:sub(aG+1)else aC=aE;aD='asc'end;if aC:match'^%$'then aC=aC:sub(2)end;aC=am(j,aC)if ak then return nil,ak end;if aD=='asc'then aD='<'else aD='>'end;aB=('%s %s %s'):format(aC:gsub('v','v1'),aD,aC:gsub('v','v2'))aA=aA:gsub('SORT_EXPR',aB)end;if I.where then aA='return function(_condn) '..aA..' end'end;if b then print(aA)end;local aH,T=a.load(aA,'tmp')if not aH then return nil,T end;aH=aH()if I.where then aH=aH(I.where)end;local aI=aH(j)if ay then g(ay,_G)local aJ={}a.setfenv(aI,aJ)setmetatable(aJ,{__index=function(aK,aL)for aa,s in ipairs(ay)do if s[aL]then return s[aL]end end end})end;return aI end;F.select=j.query;F.select_row=function(ag,I,ay)return j.query(ag,I,ay,true)end;function j.filter(aM,aN,aO,aP)local ag=j.read(aN or'stdin')local aQ=Q(aO or'stdout')local J,T=ag:select(aM)local x=ag.delim;if not J then T='error: '..T;if aP then return nil,T else a.quit(1,T)end end;while true do local t={J()}if#t==0 then break end;aQ:write(h(t,x),'\n')end end;return j
