:3
This commit is contained in:
parent
491112768c
commit
68ec37f994
66 changed files with 6591 additions and 10096 deletions
477
computer/28/lib/argparse.lua
Normal file
477
computer/28/lib/argparse.lua
Normal file
|
@ -0,0 +1,477 @@
|
|||
local function e(t,a)for o,i in pairs(a)do if type(i)=="table"then i=e({},i)end
|
||||
t[o]=i end return t end local function n(s,h,r)local d={}d.__index=d if r then
|
||||
d.__prototype=e(e({},r.__prototype),s)else d.__prototype=s end if h then local
|
||||
l={}for u,c in ipairs(h)do local m,f=c[1],c[2]d[m]=function(w,y)if not
|
||||
f(w,y)then w["_"..m]=y end return w end l[m]=true end function
|
||||
d.__call(p,...)if type((...))=="table"then for v,b in pairs((...))do if
|
||||
l[v]then p[v](p,b)end end else local g=select("#",...)for k,q in ipairs(h)do if
|
||||
k>g or k>h.args then break end local arg=select(k,...)if arg~=nil then
|
||||
p[q[1]](p,arg)end end end return p end end local j={}j.__index=r function
|
||||
j.__call(x,...)local z=e({},x.__prototype)setmetatable(z,x)return z(...)end
|
||||
return setmetatable(d,j)end local function E(T,A,O)for I,N in ipairs(A)do if
|
||||
type(O)==N then return true end end
|
||||
error(("bad property '%s' (%s expected, got %s)"):format(T,table.concat(A," or "),type(O)))end
|
||||
local function S(H,...)local R={...}return{H,function(D,L)E(H,R,L)end}end local
|
||||
U={"name",function(C,M)E("name",{"string"},M)for F in M:gmatch("%S+")do
|
||||
C._name=C._name or F
|
||||
table.insert(C._aliases,F)table.insert(C._public_aliases,F)if
|
||||
F:find("_",1,true)then table.insert(C._aliases,(F:gsub("_","-")))end end return
|
||||
true end}local W={"hidden_name",function(Y,P)E("hidden_name",{"string"},P)for V
|
||||
in P:gmatch("%S+")do table.insert(Y._aliases,V)if V:find("_",1,true)then
|
||||
table.insert(Y._aliases,(V:gsub("_","-")))end end return true end}local
|
||||
function B(G)if tonumber(G)then return tonumber(G),tonumber(G)end if G=="*"then
|
||||
return 0,math.huge end if G=="+"then return 1,math.huge end if G=="?"then
|
||||
return 0,1 end if G:match"^%d+%-%d+$"then local
|
||||
K,Q=G:match"^(%d+)%-(%d+)$"return tonumber(K),tonumber(Q)end if
|
||||
G:match"^%d+%+$"then local J=G:match"^(%d+)%+$"return tonumber(J),math.huge end
|
||||
end local function X(Z)return{Z,function(et,tt)E(Z,{"number","string"},tt)local
|
||||
at,ot=B(tt)if not at then error(("bad property '%s'"):format(Z))end
|
||||
et["_min"..Z],et["_max"..Z]=at,ot end}end local it={}local
|
||||
nt={"action",function(st,ht)E("action",{"function","string"},ht)if
|
||||
type(ht)=="string"and not it[ht]then
|
||||
error(("unknown action '%s'"):format(ht))end end}local
|
||||
rt={"init",function(dt)dt._has_init=true end}local
|
||||
lt={"default",function(ut,ct)if type(ct)~="string"then ut._init=ct
|
||||
ut._has_init=true return true end end}local
|
||||
mt={"add_help",function(ft,wt)E("add_help",{"boolean","string","table"},wt)if
|
||||
ft._help_option_idx then
|
||||
table.remove(ft._options,ft._help_option_idx)ft._help_option_idx=nil end if wt
|
||||
then local
|
||||
yt=ft:flag():description"Show this help message and exit.":action(function()print(ft:get_help())error()end)if
|
||||
wt~=true then yt=yt(wt)end if not yt._name then yt"-h""--help"end
|
||||
ft._help_option_idx=#ft._options end end}local
|
||||
pt=n({_arguments={},_options={},_commands={},_mutexes={},_groups={},_require_command=true,_handle_options=true},{args=3,S("name","string"),S("description","string"),S("epilog","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),mt})local
|
||||
vt=n({_aliases={},_public_aliases={}},{args=3,U,S("description","string"),S("epilog","string"),W,S("summary","string"),S("target","string"),S("usage","string"),S("help","string"),S("require_command","boolean"),S("handle_options","boolean"),S("action","function"),S("command_target","string"),S("help_vertical_space","number"),S("usage_margin","number"),S("usage_max_width","number"),S("help_usage_margin","number"),S("help_description_margin","number"),S("help_max_width","number"),S("hidden","boolean"),mt},pt)local
|
||||
bt=n({_minargs=1,_maxargs=1,_mincount=1,_maxcount=1,_defmode="unused",_show_default=true},{args=5,S("name","string"),S("description","string"),lt,S("convert","function","table"),X("args"),S("target","string"),S("defmode","string"),S("show_default","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt})local
|
||||
gt=n({_aliases={},_public_aliases={},_mincount=0,_overwrite=true},{args=6,U,S("description","string"),lt,S("convert","function","table"),X("args"),X("count"),W,S("target","string"),S("defmode","string"),S("show_default","boolean"),S("overwrite","boolean"),S("argname","string","table"),S("choices","table"),S("hidden","boolean"),nt,rt},bt)function
|
||||
pt:_inherit_property(kt,qt)local jt=self while true do local xt=jt["_"..kt]if
|
||||
xt~=nil then return xt end if not jt._parent then return qt end jt=jt._parent
|
||||
end end function bt:_get_argument_list()local zt={}local Et=1 while
|
||||
Et<=math.min(self._minargs,3)do local Tt=self:_get_argname(Et)if self._default
|
||||
and self._defmode:find"a"then Tt="["..Tt.."]"end table.insert(zt,Tt)Et=Et+1 end
|
||||
while Et<=math.min(self._maxargs,3)do
|
||||
table.insert(zt,"["..self:_get_argname(Et).."]")Et=Et+1 if
|
||||
self._maxargs==math.huge then break end end if Et<self._maxargs then
|
||||
table.insert(zt,"...")end return zt end function bt:_get_usage()local
|
||||
At=table.concat(self:_get_argument_list()," ")if self._default and
|
||||
self._defmode:find"u"then if self._maxargs>1 or(self._minargs==1 and not
|
||||
self._defmode:find"a")then At="["..At.."]"end end return At end function
|
||||
it.store_true(Ot,It)Ot[It]=true end function it.store_false(Nt,St)Nt[St]=false
|
||||
end function it.store(Ht,Rt,Dt)Ht[Rt]=Dt end function it.count(Lt,Ut,Ct,Mt)if
|
||||
not Mt then Lt[Ut]=Lt[Ut]+1 end end function
|
||||
it.append(Ft,Wt,Yt,Pt)Ft[Wt]=Ft[Wt]or{}table.insert(Ft[Wt],Yt)if Pt then
|
||||
table.remove(Ft[Wt],1)end end function it.concat(Vt,Bt,Gt,Kt)if Kt then
|
||||
error("'concat' action can't handle too many invocations")end
|
||||
Vt[Bt]=Vt[Bt]or{}for Qt,Jt in ipairs(Gt)do table.insert(Vt[Bt],Jt)end end
|
||||
function bt:_get_action()local Xt,Zt if self._maxcount==1 then if
|
||||
self._maxargs==0 then Xt,Zt="store_true",nil else Xt,Zt="store",nil end else if
|
||||
self._maxargs==0 then Xt,Zt="count",0 else Xt,Zt="append",{}end end if
|
||||
self._action then Xt=self._action end if self._has_init then Zt=self._init end
|
||||
if type(Xt)=="string"then Xt=it[Xt]end return Xt,Zt end function
|
||||
bt:_get_argname(ea)local ta=self._argname or self:_get_default_argname()if
|
||||
type(ta)=="table"then return ta[ea]else return ta end end function
|
||||
bt:_get_choices_list()return"{"..table.concat(self._choices,",").."}"end
|
||||
function bt:_get_default_argname()if self._choices then return
|
||||
self:_get_choices_list()else return"<"..self._name..">"end end function
|
||||
gt:_get_default_argname()if self._choices then return
|
||||
self:_get_choices_list()else return"<"..self:_get_default_target()..">"end end
|
||||
function bt:_get_label_lines()if self._choices then
|
||||
return{self:_get_choices_list()}else return{self._name}end end function
|
||||
gt:_get_label_lines()local aa=self:_get_argument_list()if#aa==0 then
|
||||
return{table.concat(self._public_aliases,", ")}end local oa=-1 for ia,na in
|
||||
ipairs(self._public_aliases)do oa=math.max(oa,#na)end local
|
||||
sa=table.concat(aa," ")local ha={}for ra,da in ipairs(self._public_aliases)do
|
||||
local la=(" "):rep(oa-#da)..da.." "..sa if ra~=#self._public_aliases then
|
||||
la=la..","end table.insert(ha,la)end return ha end function
|
||||
vt:_get_label_lines()return{table.concat(self._public_aliases,", ")}end
|
||||
function bt:_get_description()if self._default and self._show_default then if
|
||||
self._description then
|
||||
return("%s (default: %s)"):format(self._description,self._default)else
|
||||
return("default: %s"):format(self._default)end else return self._description
|
||||
or""end end function vt:_get_description()return self._summary or
|
||||
self._description or""end function gt:_get_usage()local
|
||||
ua=self:_get_argument_list()table.insert(ua,1,self._name)ua=table.concat(ua," ")if
|
||||
self._mincount==0 or self._default then ua="["..ua.."]"end return ua end
|
||||
function bt:_get_default_target()return self._name end function
|
||||
gt:_get_default_target()local ca for ma,fa in ipairs(self._public_aliases)do if
|
||||
fa:sub(1,1)==fa:sub(2,2)then ca=fa:sub(3)break end end ca=ca or
|
||||
self._name:sub(2)return(ca:gsub("-","_"))end function gt:_is_vararg()return
|
||||
self._maxargs~=self._minargs end function pt:_get_fullname(wa)local
|
||||
ya=self._parent if wa and not ya then return""end local pa={self._name}while ya
|
||||
do if not wa or ya._parent then table.insert(pa,1,ya._name)end ya=ya._parent
|
||||
end return table.concat(pa," ")end function pt:_update_charset(va)va=va or{}for
|
||||
ba,ga in ipairs(self._commands)do ga:_update_charset(va)end for ka,qa in
|
||||
ipairs(self._options)do for ka,ja in ipairs(qa._aliases)do va[ja:sub(1,1)]=true
|
||||
end end return va end function pt:argument(...)local
|
||||
xa=bt(...)table.insert(self._arguments,xa)return xa end function
|
||||
pt:option(...)local za=gt(...)table.insert(self._options,za)return za end
|
||||
function pt:flag(...)return self:option():args(0)(...)end function
|
||||
pt:command(...)local Ea=vt():add_help(true)(...)Ea._parent=self
|
||||
table.insert(self._commands,Ea)return Ea end function pt:mutex(...)local
|
||||
Ta={...}for Aa,Oa in ipairs(Ta)do local Ia=getmetatable(Oa)assert(Ia==gt or
|
||||
Ia==bt,("bad argument #%d to 'mutex' (Option or Argument expected)"):format(Aa))end
|
||||
table.insert(self._mutexes,Ta)return self end function
|
||||
pt:group(Na,...)assert(type(Na)=="string",("bad argument #1 to 'group' (string expected, got %s)"):format(type(Na)))local
|
||||
Sa={name=Na,...}for Ha,Ra in ipairs(Sa)do local
|
||||
Da=getmetatable(Ra)assert(Da==gt or Da==bt or
|
||||
Da==vt,("bad argument #%d to 'group' (Option or Argument or Command expected)"):format(Ha+1))end
|
||||
table.insert(self._groups,Sa)return self end local La="Usage: "function
|
||||
pt:get_usage()if self._usage then return self._usage end local
|
||||
Ua=self:_inherit_property("usage_margin",#La)local
|
||||
Ca=self:_inherit_property("usage_max_width",70)local
|
||||
Ma={La..self:_get_fullname()}local function Fa(Wa)if#Ma[#Ma]+1+#Wa<=Ca then
|
||||
Ma[#Ma]=Ma[#Ma].." "..Wa else Ma[#Ma+1]=(" "):rep(Ua)..Wa end end local
|
||||
Ya={}local Pa={}local Va={}local Ba={}local function Ga(Ka,Qa)if Va[Ka]then
|
||||
return end Va[Ka]=true local Ja={}for Xa,Za in ipairs(Ka)do if not Za._hidden
|
||||
and not Pa[Za]then if getmetatable(Za)==gt or Za==Qa then
|
||||
table.insert(Ja,Za:_get_usage())Pa[Za]=true end end end if#Ja==1 then
|
||||
Fa(Ja[1])elseif#Ja>1 then Fa("("..table.concat(Ja," | ")..")")end end local
|
||||
function eo(to)if not to._hidden and not Pa[to]then
|
||||
Fa(to:_get_usage())Pa[to]=true end end for ao,oo in ipairs(self._mutexes)do
|
||||
local no=false local so=false for ao,ho in ipairs(oo)do if getmetatable(ho)==gt
|
||||
then if ho:_is_vararg()then no=true end else so=true
|
||||
Ba[ho]=Ba[ho]or{}table.insert(Ba[ho],oo)end Ya[ho]=true end if not no and not
|
||||
so then Ga(oo)end end for ro,lo in ipairs(self._options)do if not Ya[lo]and not
|
||||
lo:_is_vararg()then eo(lo)end end for uo,co in ipairs(self._arguments)do local
|
||||
mo if Ya[co]then for uo,fo in ipairs(Ba[co])do if not Va[fo]then mo=fo end end
|
||||
end if mo then Ga(mo,co)else eo(co)end end for wo,yo in ipairs(self._mutexes)do
|
||||
Ga(yo)end for po,vo in ipairs(self._options)do eo(vo)end if#self._commands>0
|
||||
then if self._require_command then Fa("<command>")else Fa("[<command>]")end
|
||||
Fa("...")end return table.concat(Ma,"\n")end local function bo(go)if go==""then
|
||||
return{}end local ko={}if go:sub(-1)~="\n"then go=go.."\n"end for qo in
|
||||
go:gmatch("([^\n]*)\n")do table.insert(ko,qo)end return ko end local function
|
||||
jo(xo,zo)local Eo={}local To=xo:match("^ *")if xo:find("^ *[%*%+%-]")then
|
||||
To=To.." "..xo:match("^ *[%*%+%-]( *)")end local Ao={}local Oo=0 local Io=1
|
||||
while true do local No,So,Ho=xo:find("([^ ]+)",Io)if not No then break end
|
||||
local Ro=xo:sub(Io,No-1)Io=So+1 if(#Ao==0)or(Oo+#Ro+#Ho<=zo)then
|
||||
table.insert(Ao,Ro)table.insert(Ao,Ho)Oo=Oo+#Ro+#Ho else
|
||||
table.insert(Eo,table.concat(Ao))Ao={To,Ho}Oo=#To+#Ho end end if#Ao>0 then
|
||||
table.insert(Eo,table.concat(Ao))end if#Eo==0 then Eo[1]=""end return Eo end
|
||||
local function Do(Lo,Uo)local Co={}for Mo,Fo in ipairs(Lo)do local
|
||||
Wo=jo(Fo,Uo)for Mo,Yo in ipairs(Wo)do table.insert(Co,Yo)end end return Co end
|
||||
function pt:_get_element_help(Po)local Vo=Po:_get_label_lines()local
|
||||
Bo=bo(Po:_get_description())local Go={}local
|
||||
Ko=self:_inherit_property("help_usage_margin",1)local Qo=(" "):rep(Ko)local
|
||||
Jo=self:_inherit_property("help_description_margin",23)local
|
||||
Xo=(" "):rep(Jo)local Zo=self:_inherit_property("help_max_width")if Zo then
|
||||
local ei=math.max(Zo-Jo,10)Bo=Do(Bo,ei)end if#Vo[1]>=(Jo-Ko)then for ti,ai in
|
||||
ipairs(Vo)do table.insert(Go,Qo..ai)end for oi,ii in ipairs(Bo)do
|
||||
table.insert(Go,Xo..ii)end else for ni=1,math.max(#Vo,#Bo)do local
|
||||
si=Vo[ni]local hi=Bo[ni]local ri=""if si then ri=Qo..si end if hi and
|
||||
hi~=""then ri=ri..(" "):rep(Jo-#ri)..hi end table.insert(Go,ri)end end return
|
||||
table.concat(Go,"\n")end local function di(li)local ui={}for ci,mi in
|
||||
ipairs(li)do ui[getmetatable(mi)]=true end return ui end function
|
||||
pt:_add_group_help(fi,wi,yi,pi)local vi={yi}for bi,gi in ipairs(pi)do if not
|
||||
gi._hidden and not wi[gi]then wi[gi]=true
|
||||
table.insert(vi,self:_get_element_help(gi))end end if#vi>1 then
|
||||
table.insert(fi,table.concat(vi,("\n"):rep(self:_inherit_property("help_vertical_space",0)+1)))end
|
||||
end function pt:get_help()if self._help then return self._help end local
|
||||
ki={self:get_usage()}local qi=self:_inherit_property("help_max_width")if
|
||||
self._description then local ji=self._description if qi then
|
||||
ji=table.concat(Do(bo(ji),qi),"\n")end table.insert(ki,ji)end local
|
||||
xi={[bt]={},[gt]={},[vt]={}}for zi,Ei in ipairs(self._groups)do local
|
||||
Ti=di(Ei)for zi,Ai in ipairs({bt,gt,vt})do if Ti[Ai]then
|
||||
table.insert(xi[Ai],Ei)break end end end local
|
||||
Oi={{name="Arguments",type=bt,elements=self._arguments},{name="Options",type=gt,elements=self._options},{name="Commands",type=vt,elements=self._commands}}local
|
||||
Ii={}for Ni,Si in ipairs(Oi)do local Hi=xi[Si.type]for Ni,Ri in ipairs(Hi)do
|
||||
self:_add_group_help(ki,Ii,Ri.name..":",Ri)end local Di=Si.name..":"if#Hi>0
|
||||
then Di="Other "..Di:gsub("^.",string.lower)end
|
||||
self:_add_group_help(ki,Ii,Di,Si.elements)end if self._epilog then local
|
||||
Li=self._epilog if qi then Li=table.concat(Do(bo(Li),qi),"\n")end
|
||||
table.insert(ki,Li)end return table.concat(ki,"\n\n")end function
|
||||
pt:add_help_command(Ui)if Ui then assert(type(Ui)=="string"or
|
||||
type(Ui)=="table",("bad argument #1 to 'add_help_command' (string or table expected, got %s)"):format(type(Ui)))end
|
||||
local
|
||||
Ci=self:command():description"Show help for commands."Ci:argument"command":description"The command to show help for.":args"?":action(function(Mi,Mi,Fi)if
|
||||
not Fi then print(self:get_help())error()else for Mi,Wi in
|
||||
ipairs(self._commands)do for Mi,Yi in ipairs(Wi._aliases)do if Yi==Fi then
|
||||
print(Wi:get_help())error()end end end end
|
||||
Ci:error(("unknown command '%s'"):format(Fi))end)if Ui then Ci=Ci(Ui)end if not
|
||||
Ci._name then Ci"help"end Ci._is_help_command=true return self end function
|
||||
pt:_is_shell_safe()if self._basename then if
|
||||
self._basename:find("[^%w_%-%+%.]")then return false end else for Pi,Vi in
|
||||
ipairs(self._aliases)do if Vi:find("[^%w_%-%+%.]")then return false end end end
|
||||
for Bi,Gi in ipairs(self._options)do for Bi,Ki in ipairs(Gi._aliases)do if
|
||||
Ki:find("[^%w_%-%+%.]")then return false end end if Gi._choices then for Bi,Qi
|
||||
in ipairs(Gi._choices)do if Qi:find("[%s'\"]")then return false end end end end
|
||||
for Ji,Xi in ipairs(self._arguments)do if Xi._choices then for Ji,Zi in
|
||||
ipairs(Xi._choices)do if Zi:find("[%s'\"]")then return false end end end end
|
||||
for en,tn in ipairs(self._commands)do if not tn:_is_shell_safe()then return
|
||||
false end end return true end function pt:add_complete(an)if an then
|
||||
assert(type(an)=="string"or
|
||||
type(an)=="table",("bad argument #1 to 'add_complete' (string or table expected, got %s)"):format(type(an)))end
|
||||
local
|
||||
on=self:option():description"Output a shell completion script for the specified shell.":args(1):choices{"bash","zsh","fish"}:action(function(nn,nn,sn)io.write(self["get_"..sn.."_complete"](self))error()end)if
|
||||
an then on=on(an)end if not on._name then on"--completion"end return self end
|
||||
function pt:add_complete_command(hn)if hn then assert(type(hn)=="string"or
|
||||
type(hn)=="table",("bad argument #1 to 'add_complete_command' (string or table expected, got %s)"):format(type(hn)))end
|
||||
local
|
||||
rn=self:command():description"Output a shell completion script."rn:argument"shell":description"The shell to output a completion script for.":choices{"bash","zsh","fish"}:action(function(dn,dn,ln)io.write(self["get_"..ln.."_complete"](self))error()end)if
|
||||
hn then rn=rn(hn)end if not rn._name then rn"completion"end return self end
|
||||
local function un(cn)return cn:gsub("[/\\]*$",""):match(".*[/\\]([^/\\]*)")or
|
||||
cn end local function mn(fn)local
|
||||
wn=fn:_get_description():match("^(.-)%.%s")return wn or
|
||||
fn:_get_description():match("^(.-)%.?$")end function pt:_get_options()local
|
||||
yn={}for pn,vn in ipairs(self._options)do for pn,bn in ipairs(vn._aliases)do
|
||||
table.insert(yn,bn)end end return table.concat(yn," ")end function
|
||||
pt:_get_commands()local gn={}for kn,qn in ipairs(self._commands)do for kn,jn in
|
||||
ipairs(qn._aliases)do table.insert(gn,jn)end end return table.concat(gn," ")end
|
||||
function pt:_bash_option_args(xn,zn)local En={}for Tn,An in
|
||||
ipairs(self._options)do if An._choices or An._minargs>0 then local On if
|
||||
An._choices then
|
||||
On='COMPREPLY=($(compgen -W "'..table.concat(An._choices," ")..'" -- "$cur"))'else
|
||||
On='COMPREPLY=($(compgen -f -- "$cur"))'end
|
||||
table.insert(En,(" "):rep(zn+4)..table.concat(An._aliases,"|")..")")table.insert(En,(" "):rep(zn+8)..On)table.insert(En,(" "):rep(zn+8).."return 0")table.insert(En,(" "):rep(zn+8)..";;")end
|
||||
end if#En>0 then
|
||||
table.insert(xn,(" "):rep(zn)..'case "$prev" in')table.insert(xn,table.concat(En,"\n"))table.insert(xn,(" "):rep(zn).."esac\n")end
|
||||
end function pt:_bash_get_cmd(In,Nn)if#self._commands==0 then return end
|
||||
table.insert(In,(" "):rep(Nn)..'args=("${args[@]:1}")')table.insert(In,(" "):rep(Nn)..'for arg in "${args[@]}"; do')table.insert(In,(" "):rep(Nn+4)..'case "$arg" in')for
|
||||
Sn,Hn in ipairs(self._commands)do
|
||||
table.insert(In,(" "):rep(Nn+8)..table.concat(Hn._aliases,"|")..")")if
|
||||
self._parent then
|
||||
table.insert(In,(" "):rep(Nn+12)..'cmd="$cmd '..Hn._name..'"')else
|
||||
table.insert(In,(" "):rep(Nn+12)..'cmd="'..Hn._name..'"')end
|
||||
table.insert(In,(" "):rep(Nn+12)..'opts="$opts '..Hn:_get_options()..'"')Hn:_bash_get_cmd(In,Nn+12)table.insert(In,(" "):rep(Nn+12).."break")table.insert(In,(" "):rep(Nn+12)..";;")end
|
||||
table.insert(In,(" "):rep(Nn+4).."esac")table.insert(In,(" "):rep(Nn).."done")end
|
||||
function pt:_bash_cmd_completions(Rn)local Dn={}if self._parent then
|
||||
self:_bash_option_args(Dn,12)end if#self._commands>0 then
|
||||
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self:_get_commands()..'" -- "$cur"))')elseif
|
||||
self._is_help_command then
|
||||
table.insert(Dn,(" "):rep(12)..'COMPREPLY=($(compgen -W "'..self._parent:_get_commands()..'" -- "$cur"))')end
|
||||
if#Dn>0 then
|
||||
table.insert(Rn,(" "):rep(8).."'"..self:_get_fullname(true).."')")table.insert(Rn,table.concat(Dn,"\n"))table.insert(Rn,(" "):rep(12)..";;")end
|
||||
for Ln,Un in ipairs(self._commands)do Un:_bash_cmd_completions(Rn)end end
|
||||
function
|
||||
pt:get_bash_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
|
||||
Cn={([[
|
||||
_%s() {
|
||||
local IFS=$' \t\n'
|
||||
local args cur prev cmd opts arg
|
||||
args=("${COMP_WORDS[@]}")
|
||||
cur="${COMP_WORDS[COMP_CWORD]}"
|
||||
prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
opts="%s"
|
||||
]]):format(self._basename,self:_get_options())}self:_bash_option_args(Cn,4)self:_bash_get_cmd(Cn,4)if#self._commands>0
|
||||
then
|
||||
table.insert(Cn,"")table.insert(Cn,(" "):rep(4)..'case "$cmd" in')self:_bash_cmd_completions(Cn)table.insert(Cn,(" "):rep(4).."esac\n")end
|
||||
table.insert(Cn,([=[
|
||||
if [[ "$cur" = -* ]]; then
|
||||
COMPREPLY=($(compgen -W "$opts" -- "$cur"))
|
||||
fi
|
||||
}
|
||||
|
||||
complete -F _%s -o bashdefault -o default %s
|
||||
]=]):format(self._basename,self._basename))return
|
||||
table.concat(Cn,"\n")end function pt:_zsh_arguments(Mn,Fn,Wn)if self._parent
|
||||
then
|
||||
table.insert(Mn,(" "):rep(Wn).."options=(")table.insert(Mn,(" "):rep(Wn+2).."$options")else
|
||||
table.insert(Mn,(" "):rep(Wn).."local -a options=(")end for Yn,Pn in
|
||||
ipairs(self._options)do local Vn={}if#Pn._aliases>1 then if Pn._maxcount>1 then
|
||||
table.insert(Vn,'"*"')end
|
||||
table.insert(Vn,"{"..table.concat(Pn._aliases,",")..'}"')else
|
||||
table.insert(Vn,'"')if Pn._maxcount>1 then table.insert(Vn,"*")end
|
||||
table.insert(Vn,Pn._name)end if Pn._description then local
|
||||
Bn=mn(Pn):gsub('["%]:`$]',"\\%0")table.insert(Vn,"["..Bn.."]")end if
|
||||
Pn._maxargs==math.huge then table.insert(Vn,":*")end if Pn._choices then
|
||||
table.insert(Vn,": :("..table.concat(Pn._choices," ")..")")elseif Pn._maxargs>0
|
||||
then table.insert(Vn,": :_files")end
|
||||
table.insert(Vn,'"')table.insert(Mn,(" "):rep(Wn+2)..table.concat(Vn))end
|
||||
table.insert(Mn,(" "):rep(Wn)..")")table.insert(Mn,(" "):rep(Wn).."_arguments -s -S \\")table.insert(Mn,(" "):rep(Wn+2).."$options \\")if
|
||||
self._is_help_command then
|
||||
table.insert(Mn,(" "):rep(Wn+2)..'": :('..self._parent:_get_commands()..')" \\')else
|
||||
for Gn,Kn in ipairs(self._arguments)do local Qn if Kn._choices then
|
||||
Qn=": :("..table.concat(Kn._choices," ")..")"else Qn=": :_files"end if
|
||||
Kn._maxargs==math.huge then
|
||||
table.insert(Mn,(" "):rep(Wn+2)..'"*'..Qn..'" \\')break end for
|
||||
Gn=1,Kn._maxargs do table.insert(Mn,(" "):rep(Wn+2)..'"'..Qn..'" \\')end end
|
||||
if#self._commands>0 then
|
||||
table.insert(Mn,(" "):rep(Wn+2)..'": :_'..Fn..'_cmds" \\')table.insert(Mn,(" "):rep(Wn+2)..'"*:: :->args" \\')end
|
||||
end table.insert(Mn,(" "):rep(Wn+2).."&& return 0")end function
|
||||
pt:_zsh_cmds(Jn,Xn)table.insert(Jn,"\n_"..Xn.."_cmds() {")table.insert(Jn," local -a commands=(")for
|
||||
Zn,es in ipairs(self._commands)do local ts={}if#es._aliases>1 then
|
||||
table.insert(ts,"{"..table.concat(es._aliases,",")..'}"')else
|
||||
table.insert(ts,'"'..es._name)end if es._description then
|
||||
table.insert(ts,":"..mn(es):gsub('["`$]',"\\%0"))end
|
||||
table.insert(Jn," "..table.concat(ts)..'"')end
|
||||
table.insert(Jn,' )\n _describe "command" commands\n}')end function
|
||||
pt:_zsh_complete_help(as,os,is,ns)if#self._commands==0 then return end
|
||||
self:_zsh_cmds(os,is)table.insert(as,"\n"..(" "):rep(ns).."case $words[1] in")for
|
||||
ss,hs in ipairs(self._commands)do local rs=is.."_"..hs._name
|
||||
table.insert(as,(" "):rep(ns+2)..table.concat(hs._aliases,"|")..")")hs:_zsh_arguments(as,rs,ns+4)hs:_zsh_complete_help(as,os,rs,ns+4)table.insert(as,(" "):rep(ns+4)..";;\n")end
|
||||
table.insert(as,(" "):rep(ns).."esac")end function
|
||||
pt:get_zsh_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
|
||||
ds={("#compdef %s\n"):format(self._basename)}local
|
||||
ls={}table.insert(ds,"_"..self._basename.."() {")if#self._commands>0 then
|
||||
table.insert(ds," local context state state_descr line")table.insert(ds," typeset -A opt_args\n")end
|
||||
self:_zsh_arguments(ds,self._basename,2)self:_zsh_complete_help(ds,ls,self._basename,2)table.insert(ds,"\n return 1")table.insert(ds,"}")local
|
||||
us=table.concat(ds,"\n")if#ls>0 then us=us.."\n"..table.concat(ls,"\n")end
|
||||
return us.."\n\n_"..self._basename.."\n"end local function cs(ms)return
|
||||
ms:gsub("[\\']","\\%0")end function pt:_fish_get_cmd(fs,ws)if#self._commands==0
|
||||
then return end
|
||||
table.insert(fs,(" "):rep(ws).."set -e cmdline[1]")table.insert(fs,(" "):rep(ws).."for arg in $cmdline")table.insert(fs,(" "):rep(ws+4).."switch $arg")for
|
||||
ys,ps in ipairs(self._commands)do
|
||||
table.insert(fs,(" "):rep(ws+8).."case "..table.concat(ps._aliases," "))table.insert(fs,(" "):rep(ws+12).."set cmd $cmd "..ps._name)ps:_fish_get_cmd(fs,ws+12)table.insert(fs,(" "):rep(ws+12).."break")end
|
||||
table.insert(fs,(" "):rep(ws+4).."end")table.insert(fs,(" "):rep(ws).."end")end
|
||||
function pt:_fish_complete_help(vs,bs)local gs="complete -c "..bs
|
||||
table.insert(vs,"")for ks,qs in ipairs(self._commands)do local
|
||||
js=table.concat(qs._aliases," ")local xs if self._parent then
|
||||
xs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),js)else
|
||||
xs=("%s -n '__fish_%s_using_command' -xa '%s'"):format(gs,bs,js)end if
|
||||
qs._description then xs=("%s -d '%s'"):format(xs,cs(mn(qs)))end
|
||||
table.insert(vs,xs)end if self._is_help_command then local
|
||||
zs=("%s -n '__fish_%s_using_command %s' -xa '%s'"):format(gs,bs,self:_get_fullname(true),self._parent:_get_commands())table.insert(vs,zs)end
|
||||
for Es,Ts in ipairs(self._options)do local As={gs}if self._parent then
|
||||
table.insert(As,"-n '__fish_"..bs.."_seen_command "..self:_get_fullname(true).."'")end
|
||||
for Es,Os in ipairs(Ts._aliases)do if Os:match("^%-.$")then
|
||||
table.insert(As,"-s "..Os:sub(2))elseif Os:match("^%-%-.+")then
|
||||
table.insert(As,"-l "..Os:sub(3))end end if Ts._choices then
|
||||
table.insert(As,"-xa '"..table.concat(Ts._choices," ").."'")elseif
|
||||
Ts._minargs>0 then table.insert(As,"-r")end if Ts._description then
|
||||
table.insert(As,"-d '"..cs(mn(Ts)).."'")end
|
||||
table.insert(vs,table.concat(As," "))end for Is,Ns in ipairs(self._commands)do
|
||||
Ns:_fish_complete_help(vs,bs)end end function
|
||||
pt:get_fish_complete()self._basename=un(self._name)assert(self:_is_shell_safe())local
|
||||
Ss={}if#self._commands>0 then
|
||||
table.insert(Ss,([[
|
||||
function __fish_%s_print_command
|
||||
set -l cmdline (commandline -poc)
|
||||
set -l cmd]]):format(self._basename))self:_fish_get_cmd(Ss,4)table.insert(Ss,([[
|
||||
echo "$cmd"
|
||||
end
|
||||
|
||||
function __fish_%s_using_command
|
||||
test (__fish_%s_print_command) = "$argv"
|
||||
and return 0
|
||||
or return 1
|
||||
end
|
||||
|
||||
function __fish_%s_seen_command
|
||||
string match -q "$argv*" (__fish_%s_print_command)
|
||||
and return 0
|
||||
or return 1
|
||||
end]]):format(self._basename,self._basename,self._basename,self._basename))end
|
||||
self:_fish_complete_help(Ss,self._basename)return
|
||||
table.concat(Ss,"\n").."\n"end local function Hs(Rs,Ds)local Ls={}local Us
|
||||
local Cs={}for Ms in pairs(Rs)do if type(Ms)=="string"then for Fs=1,#Ms do
|
||||
Us=Ms:sub(1,Fs-1)..Ms:sub(Fs+1)if not Ls[Us]then Ls[Us]={}end
|
||||
table.insert(Ls[Us],Ms)end end end for Ws=1,#Ds+1 do
|
||||
Us=Ds:sub(1,Ws-1)..Ds:sub(Ws+1)if Rs[Us]then Cs[Us]=true elseif Ls[Us]then for
|
||||
Ys,Ps in ipairs(Ls[Us])do Cs[Ps]=true end end end local Vs=next(Cs)if Vs then
|
||||
if next(Cs,Vs)then local Bs={}for Gs in pairs(Cs)do
|
||||
table.insert(Bs,"'"..Gs.."'")end
|
||||
table.sort(Bs)return"\nDid you mean one of these: "..table.concat(Bs," ").."?"else
|
||||
return"\nDid you mean '"..Vs.."'?"end else return""end end local
|
||||
Ks=n({invocations=0})function Ks:__call(Qs,Js)self.state=Qs
|
||||
self.result=Qs.result self.element=Js self.target=Js._target or
|
||||
Js:_get_default_target()self.action,self.result[self.target]=Js:_get_action()return
|
||||
self end function Ks:error(Xs,...)self.state:error(Xs,...)end function
|
||||
Ks:convert(Zs,eh)local th=self.element._convert if th then local ah,oh if
|
||||
type(th)=="function"then ah,oh=th(Zs)elseif type(th[eh])=="function"then
|
||||
ah,oh=th[eh](Zs)else ah=th[Zs]end if ah==nil then self:error(oh
|
||||
and"%s"or"malformed argument '%s'",oh or Zs)end Zs=ah end return Zs end
|
||||
function Ks:default(ih)return self.element._defmode:find(ih)and
|
||||
self.element._default end local function nh(sh,hh,rh,dh)local lh=""if hh~=rh
|
||||
then lh="at "..(dh and"most"or"least").." "end local uh=dh and rh or hh return
|
||||
lh..tostring(uh).." "..sh..(uh==1 and""or"s")end function
|
||||
Ks:set_name(ch)self.name=("%s '%s'"):format(ch and"option"or"argument",ch or
|
||||
self.element._name)end function Ks:invoke()self.open=true self.overwrite=false
|
||||
if self.invocations>=self.element._maxcount then if self.element._overwrite
|
||||
then self.overwrite=true else local
|
||||
mh=nh("time",self.element._mincount,self.element._maxcount,true)self:error("%s must be used %s",self.name,mh)end
|
||||
else self.invocations=self.invocations+1 end self.args={}if
|
||||
self.element._maxargs<=0 then self:close()end return self.open end function
|
||||
Ks:check_choices(fh)if self.element._choices then for wh,yh in
|
||||
ipairs(self.element._choices)do if fh==yh then return end end local
|
||||
ph="'"..table.concat(self.element._choices,"', '").."'"local
|
||||
vh=getmetatable(self.element)==gt self:error("%s%s must be one of %s",vh
|
||||
and"argument for "or"",self.name,ph)end end function
|
||||
Ks:pass(bh)self:check_choices(bh)bh=self:convert(bh,#self.args+1)table.insert(self.args,bh)if#self.args>=self.element._maxargs
|
||||
then self:close()end return self.open end function
|
||||
Ks:complete_invocation()while#self.args<self.element._minargs do
|
||||
self:pass(self.element._default)end end function Ks:close()if self.open then
|
||||
self.open=false if#self.args<self.element._minargs then if
|
||||
self:default("a")then self:complete_invocation()else if#self.args==0 then if
|
||||
getmetatable(self.element)==bt then self:error("missing %s",self.name)elseif
|
||||
self.element._maxargs==1 then
|
||||
self:error("%s requires an argument",self.name)end end
|
||||
self:error("%s requires %s",self.name,nh("argument",self.element._minargs,self.element._maxargs))end
|
||||
end local gh if self.element._maxargs==0 then gh=self.args[1]elseif
|
||||
self.element._maxargs==1 then if self.element._minargs==0 and
|
||||
self.element._mincount~=self.element._maxcount then gh=self.args else
|
||||
gh=self.args[1]end else gh=self.args end
|
||||
self.action(self.result,self.target,gh,self.overwrite)end end local
|
||||
kh=n({result={},options={},arguments={},argument_i=1,element_to_mutexes={},mutex_to_element_state={},command_actions={}})function
|
||||
kh:__call(qh,jh)self.parser=qh self.error_handler=jh
|
||||
self.charset=qh:_update_charset()self:switch(qh)return self end function
|
||||
kh:error(xh,...)self.error_handler(self.parser,xh:format(...))end function
|
||||
kh:switch(zh)self.parser=zh if zh._action then
|
||||
table.insert(self.command_actions,{action=zh._action,name=zh._name})end for
|
||||
Eh,Th in ipairs(zh._options)do Th=Ks(self,Th)table.insert(self.options,Th)for
|
||||
Eh,Ah in ipairs(Th.element._aliases)do self.options[Ah]=Th end end for Oh,Ih in
|
||||
ipairs(zh._mutexes)do for Oh,Nh in ipairs(Ih)do if not
|
||||
self.element_to_mutexes[Nh]then self.element_to_mutexes[Nh]={}end
|
||||
table.insert(self.element_to_mutexes[Nh],Ih)end end for Sh,Hh in
|
||||
ipairs(zh._arguments)do
|
||||
Hh=Ks(self,Hh)table.insert(self.arguments,Hh)Hh:set_name()Hh:invoke()end
|
||||
self.handle_options=zh._handle_options
|
||||
self.argument=self.arguments[self.argument_i]self.commands=zh._commands for
|
||||
Rh,Dh in ipairs(self.commands)do for Rh,Lh in ipairs(Dh._aliases)do
|
||||
self.commands[Lh]=Dh end end end function kh:get_option(Uh)local
|
||||
Ch=self.options[Uh]if not Ch then
|
||||
self:error("unknown option '%s'%s",Uh,Hs(self.options,Uh))else return Ch end
|
||||
end function kh:get_command(Mh)local Fh=self.commands[Mh]if not Fh then
|
||||
if#self.commands>0 then
|
||||
self:error("unknown command '%s'%s",Mh,Hs(self.commands,Mh))else
|
||||
self:error("too many arguments")end else return Fh end end function
|
||||
kh:check_mutexes(Wh)if self.element_to_mutexes[Wh.element]then for Yh,Ph in
|
||||
ipairs(self.element_to_mutexes[Wh.element])do local
|
||||
Vh=self.mutex_to_element_state[Ph]if Vh and Vh~=Wh then
|
||||
self:error("%s can not be used together with %s",Wh.name,Vh.name)else
|
||||
self.mutex_to_element_state[Ph]=Wh end end end end function
|
||||
kh:invoke(Bh,Gh)self:close()Bh:set_name(Gh)self:check_mutexes(Bh,Gh)if
|
||||
Bh:invoke()then self.option=Bh end end function kh:pass(Kh)if self.option then
|
||||
if not self.option:pass(Kh)then self.option=nil end elseif self.argument then
|
||||
self:check_mutexes(self.argument)if not self.argument:pass(Kh)then
|
||||
self.argument_i=self.argument_i+1
|
||||
self.argument=self.arguments[self.argument_i]end else local
|
||||
Qh=self:get_command(Kh)self.result[Qh._target or Qh._name]=true if
|
||||
self.parser._command_target then
|
||||
self.result[self.parser._command_target]=Qh._name end self:switch(Qh)end end
|
||||
function kh:close()if self.option then self.option:close()self.option=nil end
|
||||
end function kh:finalize()self:close()for Jh=self.argument_i,#self.arguments do
|
||||
local Xh=self.arguments[Jh]if#Xh.args==0 and Xh:default("u")then
|
||||
Xh:complete_invocation()else Xh:close()end end if self.parser._require_command
|
||||
and#self.commands>0 then self:error("a command is required")end for Zh,er in
|
||||
ipairs(self.options)do er.name=er.name
|
||||
or("option '%s'"):format(er.element._name)if er.invocations==0 then if
|
||||
er:default("u")then er:invoke()er:complete_invocation()er:close()end end local
|
||||
tr=er.element._mincount if er.invocations<tr then if er:default("a")then while
|
||||
er.invocations<tr do er:invoke()er:close()end elseif er.invocations==0 then
|
||||
self:error("missing %s",er.name)else
|
||||
self:error("%s must be used %s",er.name,nh("time",tr,er.element._maxcount))end
|
||||
end end for ar=#self.command_actions,1,-1 do
|
||||
self.command_actions[ar].action(self.result,self.command_actions[ar].name)end
|
||||
end function kh:parse(ir)for nr,sr in ipairs(ir)do local hr=true if
|
||||
self.handle_options then local rr=sr:sub(1,1)if self.charset[rr]then if#sr>1
|
||||
then hr=false if sr:sub(2,2)==rr then if#sr==2 then if self.options[sr]then
|
||||
local dr=self:get_option(sr)self:invoke(dr,sr)else self:close()end
|
||||
self.handle_options=false else local lr=sr:find"="if lr then local
|
||||
ur=sr:sub(1,lr-1)local cr=self:get_option(ur)if cr.element._maxargs<=0 then
|
||||
self:error("option '%s' does not take arguments",ur)end
|
||||
self:invoke(cr,ur)self:pass(sr:sub(lr+1))else local
|
||||
mr=self:get_option(sr)self:invoke(mr,sr)end end else for fr=2,#sr do local
|
||||
wr=rr..sr:sub(fr,fr)local yr=self:get_option(wr)self:invoke(yr,wr)if fr~=#sr
|
||||
and yr.element._maxargs>0 then self:pass(sr:sub(fr+1))break end end end end end
|
||||
end if hr then self:pass(sr)end end self:finalize()return self.result end
|
||||
function
|
||||
pt:error(pr)io.stderr:write(("%s\n\nError: %s\n"):format(self:get_usage(),pr))error()end
|
||||
local vr=rawget(_G,"arg")or{}function pt:_parse(br,gr)return
|
||||
kh(self,gr):parse(br or vr)end function pt:parse(kr)return
|
||||
self:_parse(kr,self.error)end local function qr(jr)return
|
||||
tostring(jr).."\noriginal "..debug.traceback("",2):sub(2)end function
|
||||
pt:pparse(xr)local zr local Er,Tr=xpcall(function()return
|
||||
self:_parse(xr,function(Ar,Or)zr=Or error(Or,0)end)end,qr)if Er then return
|
||||
true,Tr elseif not zr then error(Tr,0)else return false,zr end end local
|
||||
Ir={}Ir.version="0.7.3"setmetatable(Ir,{__call=function(Nr,...)return
|
||||
pt(vr[0]):add_help(true)(...)end})return
|
||||
Ir
|
84
computer/28/lib/numberformatter.lua
Normal file
84
computer/28/lib/numberformatter.lua
Normal file
|
@ -0,0 +1,84 @@
|
|||
--[[- Library for formatting numbers
|
||||
@module numberformatter
|
||||
]]
|
||||
|
||||
--[[ numberformatter.lua
|
||||
_ _ _ _ _ _ ___ ____ ____
|
||||
|\ | | | |\/| |__] |___ |__/
|
||||
| \| |__| | | |__] |___ | \
|
||||
____ ____ ____ _ _ ____ ___ ___ ____ ____
|
||||
|___ | | |__/ |\/| |__| | | |___ |__/
|
||||
| |__| | \ | | | | | | |___ | \
|
||||
]]
|
||||
|
||||
local NumberFormatter = {
|
||||
--- "Metadata" - Version
|
||||
_VERSION = "1.1.0",
|
||||
--- "Metadata" - Description
|
||||
_DESCRIPTION = "Library for formatting numbers",
|
||||
--- "Metadata" - Homepage / Url
|
||||
_URL = "https://github.com/Commandcracker/YouCube",
|
||||
--- "Metadata" - License
|
||||
_LICENSE = "GPL-3.0"
|
||||
}
|
||||
--[[
|
||||
NumberFormatter.compact and NumberFormatter.abbreviate based on:
|
||||
https://devforum.roblox.com/t/how-can-i-turn-a-number-to-a-shorter-number-i-dont-know-how-to-explain-click-to-understand-3/649496/3
|
||||
]]
|
||||
|
||||
local Suffixes = { "k", "M", "B", "T", "qd", "Qn", "sx", "Sp", "O", "N", "de", "Ud", "DD", "tdD", "qdD", "QnD", "sxD",
|
||||
"SpD", "OcD", "NvD", "Vgn", "UVg", "DVg", "TVg", "qtV", "QnV", "SeV", "SPG", "OVG", "NVG", "TGN", "UTG", "DTG",
|
||||
"tsTG", "qtTG", "QnTG", "ssTG", "SpTG", "OcTG", "NoTG", "QdDR", "uQDR", "dQDR", "tQDR", "qdQDR", "QnQDR", "sxQDR",
|
||||
"SpQDR", "OQDDr", "NQDDr", "qQGNT", "uQGNT", "dQGNT", "tQGNT", "qdQGNT", "QnQGNT", "sxQGNT", "SpQGNT", "OQQGNT",
|
||||
"NQQGNT", "SXGNTL" }
|
||||
|
||||
--[[- Format number by LDML's specification for [Compact Number Formats](http://unicode.org/reports/tr35/tr35-numbers.html#Compact_Number_Formats)
|
||||
@tparam number number The number to format
|
||||
@treturn string formatted number
|
||||
@usage Example:
|
||||
|
||||
local numberformatter = require("numberformatter")
|
||||
print(numberformatter.compact(1000))
|
||||
|
||||
Output: `1k`
|
||||
]]
|
||||
function NumberFormatter.compact(number)
|
||||
local Negative = number < 0
|
||||
number = math.abs(number)
|
||||
|
||||
local Paired = false
|
||||
for i in pairs(Suffixes) do
|
||||
if not (number >= 10 ^ (3 * i)) then
|
||||
number = number / 10 ^ (3 * (i - 1))
|
||||
local isComplex = string.find(tostring(number), ".") and string.sub(tostring(number), 4, 4) ~= "."
|
||||
number = string.sub(tostring(number), 1, isComplex and 4 or 3) .. (Suffixes[i - 1] or "")
|
||||
Paired = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if not Paired then
|
||||
local Rounded = math.floor(number)
|
||||
number = tostring(Rounded)
|
||||
end
|
||||
if Negative then
|
||||
return "-" .. number
|
||||
end
|
||||
return number -- returns 1.0k for example
|
||||
end
|
||||
|
||||
--[[- Format number - separate thousands by comma
|
||||
@tparam number number The number to format
|
||||
@treturn string formatted number
|
||||
@usage Example:
|
||||
|
||||
local numberformatter = require("numberformatter")
|
||||
print(numberformatter.abbreviate(1000))
|
||||
|
||||
Output: `1,000`
|
||||
]]
|
||||
function NumberFormatter.abbreviate(number)
|
||||
local left, num, right = string.match(number, '^([^%d]*%d)(%d*)(.-)$')
|
||||
return left .. num:reverse():gsub('(%d%d%d)', '%1,'):reverse() .. right
|
||||
end
|
||||
|
||||
return NumberFormatter
|
68
computer/28/lib/semver.lua
Normal file
68
computer/28/lib/semver.lua
Normal file
|
@ -0,0 +1,68 @@
|
|||
local
|
||||
e={_VERSION='1.2.1',_DESCRIPTION='semver for Lua',_URL='https://github.com/kikito/semver.lua',_LICENSE=[[
|
||||
MIT LICENSE
|
||||
|
||||
Copyright (c) 2015 Enrique García Cota
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of tother 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 tother 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
|
||||
function
|
||||
t(a,o)assert(a>=0,o..' must be a valid positive number')assert(math.floor(a)==a,o..' must be an integer')end
|
||||
local function i(n)return n and n~=''end local function s(h)h=h or""local
|
||||
r,d={},0 h:gsub("([^%.]+)",function(l)d=d+1 r[d]=l end)return r end local
|
||||
function u(c)local m,f=c:match("^(-[^+]+)(+.+)$")if not(m and f)then
|
||||
m=c:match("^(-.+)$")f=c:match("^(+.+)$")end assert(m or
|
||||
f,("The parameter %q must begin with + or - to denote a prerelease or a build"):format(c))return
|
||||
m,f end local function w(y)if y then local
|
||||
p=y:match("^-(%w[%.%w-]*)$")assert(p,("The prerelease %q is not a slash followed by alphanumerics, dots and slashes"):format(y))return
|
||||
p end end local function v(b)if b then local
|
||||
g=b:match("^%+(%w[%.%w-]*)$")assert(g,("The build %q is not a + sign followed by alphanumerics, dots and slashes"):format(b))return
|
||||
g end end local function k(q)if not i(q)then return nil,nil end local
|
||||
j,x=u(q)local z=w(j)local E=v(x)return z,E end local function T(A)local
|
||||
O,I,N,S=A:match("^(%d+)%.?(%d*)%.?(%d*)(.-)$")assert(type(O)=='string',("Could not extract version number(s) from %q"):format(A))local
|
||||
H,R,D=tonumber(O),tonumber(I),tonumber(N)local L,U=k(S)return H,R,D,L,U end
|
||||
local function C(M,F)return M==F and 0 or M<F and-1 or 1 end local function
|
||||
W(Y,P)if Y==P then return 0 elseif not Y then return-1 elseif not P then return
|
||||
1 end local V,B=tonumber(Y),tonumber(P)if V and B then return C(V,B)elseif V
|
||||
then return-1 elseif B then return 1 else return C(Y,P)end end local function
|
||||
G(K,Q)local J=#K local X for Z=1,J do X=W(K[Z],Q[Z])if X~=0 then return X==-1
|
||||
end end return J<#Q end local function et(tt,at)if tt==at or not tt then return
|
||||
false elseif not at then return true end return G(s(tt),s(at))end local
|
||||
ot={}function ot:nextMajor()return e(self.major+1,0,0)end function
|
||||
ot:nextMinor()return e(self.major,self.minor+1,0)end function
|
||||
ot:nextPatch()return e(self.major,self.minor,self.patch+1)end local
|
||||
it={__index=ot}function it:__eq(nt)return self.major==nt.major and
|
||||
self.minor==nt.minor and self.patch==nt.patch and
|
||||
self.prerelease==nt.prerelease end function it:__lt(st)if self.major~=st.major
|
||||
then return self.major<st.major end if self.minor~=st.minor then return
|
||||
self.minor<st.minor end if self.patch~=st.patch then return self.patch<st.patch
|
||||
end return et(self.prerelease,st.prerelease)end function it:__pow(ht)if
|
||||
self.major==0 then return self==ht end return self.major==ht.major and
|
||||
self.minor<=ht.minor end function it:__tostring()local
|
||||
rt={("%d.%d.%d"):format(self.major,self.minor,self.patch)}if self.prerelease
|
||||
then table.insert(rt,"-"..self.prerelease)end if self.build then
|
||||
table.insert(rt,"+"..self.build)end return table.concat(rt)end local function
|
||||
dt(lt,ut,ct,mt,ft)assert(lt,"At least one parameter is needed")if
|
||||
type(lt)=='string'then lt,ut,ct,mt,ft=T(lt)end ct=ct or 0 ut=ut or 0
|
||||
t(lt,"major")t(ut,"minor")t(ct,"patch")local
|
||||
wt={major=lt,minor=ut,patch=ct,prerelease=mt,build=ft}return
|
||||
setmetatable(wt,it)end setmetatable(e,{__call=function(yt,...)return
|
||||
dt(...)end})e._VERSION=e(e._VERSION)return
|
||||
e
|
562
computer/28/lib/string_pack.lua
Normal file
562
computer/28/lib/string_pack.lua
Normal file
|
@ -0,0 +1,562 @@
|
|||
-- MIT License
|
||||
--
|
||||
-- Copyright (c) 2021 JackMacWindows
|
||||
--
|
||||
-- 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.
|
||||
|
||||
-- If not using CC, replace `expect` with a suitable argument checking function.
|
||||
--local expect = require "cc.expect".expect
|
||||
local expect = dofile "/rom/modules/main/cc/expect.lua".expect
|
||||
|
||||
local ByteOrder = {BIG_ENDIAN = 1, LITTLE_ENDIAN = 2}
|
||||
local isint = {b = 1, B = 1, h = 1, H = 1, l = 1, L = 1, j = 1, J = 1, T = 1}
|
||||
local packoptsize_tbl = {b = 1, B = 1, x = 1, h = 2, H = 2, f = 4, j = 4, J = 4, l = 8, L = 8, T = 8, d = 8, n = 8}
|
||||
|
||||
local function round(n) if n % 1 >= 0.5 then return math.ceil(n) else return math.floor(n) end end
|
||||
|
||||
local function floatToRawIntBits(f)
|
||||
if f == 0 then return 0
|
||||
elseif f == -0 then return 0x80000000
|
||||
elseif f == math.huge then return 0x7F800000
|
||||
elseif f == -math.huge then return 0xFF800000 end
|
||||
local m, e = math.frexp(f)
|
||||
if e > 127 or e < -126 then error("number out of range", 3) end
|
||||
e, m = e + 126, round((math.abs(m) - 0.5) * 0x1000000)
|
||||
if m > 0x7FFFFF then e = e + 1 end
|
||||
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0xFF), 23), bit32.band(m, 0x7FFFFF))
|
||||
end
|
||||
|
||||
local function doubleToRawLongBits(f)
|
||||
if f == 0 then return 0, 0
|
||||
elseif f == -0 then return 0x80000000, 0
|
||||
elseif f == math.huge then return 0x7FF00000, 0
|
||||
elseif f == -math.huge then return 0xFFF00000, 0 end
|
||||
local m, e = math.frexp(f)
|
||||
if e > 1023 or e < -1022 then error("number out of range", 3) end
|
||||
e, m = e + 1022, round((math.abs(m) - 0.5) * 0x20000000000000)
|
||||
if m > 0xFFFFFFFFFFFFF then e = e + 1 end
|
||||
return bit32.bor(f < 0 and 0x80000000 or 0, bit32.lshift(bit32.band(e, 0x7FF), 20), bit32.band(m / 0x100000000, 0xFFFFF)), bit32.band(m, 0xFFFFFFFF)
|
||||
end
|
||||
|
||||
local function intBitsToFloat(l)
|
||||
if l == 0 then return 0
|
||||
elseif l == 0x80000000 then return -0
|
||||
elseif l == 0x7F800000 then return math.huge
|
||||
elseif l == 0xFF800000 then return -math.huge end
|
||||
local m, e = bit32.band(l, 0x7FFFFF), bit32.band(bit32.rshift(l, 23), 0xFF)
|
||||
e, m = e - 126, m / 0x1000000 + 0.5
|
||||
local n = math.ldexp(m, e)
|
||||
return bit32.btest(l, 0x80000000) and -n or n
|
||||
end
|
||||
|
||||
local function longBitsToDouble(lh, ll)
|
||||
if lh == 0 and ll == 0 then return 0
|
||||
elseif lh == 0x80000000 and ll == 0 then return -0
|
||||
elseif lh == 0x7FF00000 and ll == 0 then return math.huge
|
||||
elseif lh == 0xFFF00000 and ll == 0 then return -math.huge end
|
||||
local m, e = bit32.band(lh, 0xFFFFF) * 0x100000000 + bit32.band(ll, 0xFFFFFFFF), bit32.band(bit32.rshift(lh, 20), 0x7FF)
|
||||
e, m = e - 1022, m / 0x20000000000000 + 0.5
|
||||
local n = math.ldexp(m, e)
|
||||
return bit32.btest(lh, 0x80000000) and -n or n
|
||||
end
|
||||
|
||||
local function packint(num, size, output, offset, alignment, endianness, signed)
|
||||
local total_size = 0
|
||||
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
|
||||
local i = 0
|
||||
while offset % math.min(size, alignment) ~= 0 and i < alignment do
|
||||
output[offset] = 0
|
||||
offset = offset + 1
|
||||
total_size = total_size + 1
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
if endianness == ByteOrder.BIG_ENDIAN then
|
||||
local added_padding = 0
|
||||
if size > 8 then for i = 0, size - 9 do
|
||||
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
|
||||
added_padding = added_padding + 1
|
||||
total_size = total_size + 1
|
||||
end end
|
||||
for i = added_padding, size - 1 do
|
||||
output[offset + i] = bit32.band(bit32.rshift(num, ((size - i - 1) * 8)), 0xFF)
|
||||
total_size = total_size + 1
|
||||
end
|
||||
else
|
||||
for i = 0, math.min(size, 8) - 1 do
|
||||
output[offset + i] = num / 2^(i * 8) % 256
|
||||
total_size = total_size + 1
|
||||
end
|
||||
for i = 8, size - 1 do
|
||||
output[offset + i] = (signed and num >= 2^(size * 8 - 1) ~= 0) and 0xFF or 0
|
||||
total_size = total_size + 1
|
||||
end
|
||||
end
|
||||
return total_size
|
||||
end
|
||||
|
||||
local function unpackint(str, offset, size, endianness, alignment, signed)
|
||||
local result, rsize = 0, 0
|
||||
if offset % math.min(size, alignment) ~= 0 and alignment > 1 then
|
||||
for i = 0, alignment - 1 do
|
||||
if offset % math.min(size, alignment) == 0 then break end
|
||||
offset = offset + 1
|
||||
rsize = rsize + 1
|
||||
end
|
||||
end
|
||||
for i = 0, size - 1 do
|
||||
result = result + str:byte(offset + i) * 2^((endianness == ByteOrder.BIG_ENDIAN and size - i - 1 or i) * 8)
|
||||
rsize = rsize + 1
|
||||
end
|
||||
if (signed and result >= 2^(size * 8 - 1)) then result = result - 2^(size * 8) end
|
||||
return result, rsize
|
||||
end
|
||||
|
||||
local function packoptsize(opt, alignment)
|
||||
local retval = packoptsize_tbl[opt] or 0
|
||||
if (alignment > 1 and retval % alignment ~= 0) then retval = retval + (alignment - (retval % alignment)) end
|
||||
return retval
|
||||
end
|
||||
|
||||
--[[
|
||||
* string.pack (fmt, v1, v2, ...)
|
||||
*
|
||||
* Returns a binary string containing the values v1, v2, etc.
|
||||
* serialized in binary form (packed) according to the format string fmt.
|
||||
]]
|
||||
local function pack(...)
|
||||
local fmt = expect(1, ..., "string")
|
||||
local endianness = ByteOrder.LITTLE_ENDIAN
|
||||
local alignment = 1
|
||||
local pos = 1
|
||||
local argnum = 2
|
||||
local output = {}
|
||||
local i = 1
|
||||
while i <= #fmt do
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c == '=' or c == '<' then
|
||||
endianness = ByteOrder.LITTLE_ENDIAN
|
||||
elseif c == '>' then
|
||||
endianness = ByteOrder.BIG_ENDIAN
|
||||
elseif c == '!' then
|
||||
local size = -1
|
||||
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16 or size == 0) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size == -1) then alignment = 4
|
||||
else alignment = size end
|
||||
elseif isint[c] then
|
||||
local num = expect(argnum, select(argnum, ...), "number")
|
||||
argnum = argnum + 1
|
||||
if (num >= math.pow(2, (packoptsize(c, 0) * 8 - (c:match("%l") and 1 or 0))) or
|
||||
num < (c:match("%l") and -math.pow(2, (packoptsize(c, 0) * 8 - 1)) or 0)) then
|
||||
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
|
||||
end
|
||||
pos = pos + packint(num, packoptsize(c, 0), output, pos, alignment, endianness, false)
|
||||
elseif c:lower() == 'i' then
|
||||
local signed = c == 'i'
|
||||
local size = -1
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (math.max(size, 0) * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16 or size == 0) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
|
||||
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
|
||||
elseif (size == -1) then size = 4 end
|
||||
local num = expect(argnum, select(argnum, ...), "number")
|
||||
argnum = argnum + 1
|
||||
if (num >= math.pow(2, (size * 8 - (c:match("%l") and 1 or 0))) or
|
||||
num < (c:match("%l") and -math.pow(2, (size * 8 - 1)) or 0)) then
|
||||
error(string.format("bad argument #%d to 'pack' (integer overflow)", argnum - 1), 2)
|
||||
end
|
||||
pos = pos + packint(num, size, output, pos, alignment, endianness, signed)
|
||||
elseif c == 'f' then
|
||||
local f = expect(argnum, select(argnum, ...), "number")
|
||||
argnum = argnum + 1
|
||||
local l = floatToRawIntBits(f)
|
||||
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 0, alignment - 1 do
|
||||
if pos % math.min(4, alignment) == 0 then break end
|
||||
output[pos] = 0
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 3 - j or j)] = bit32.band(bit32.rshift(l, (j * 8)), 0xFF) end
|
||||
pos = pos + 4
|
||||
elseif c == 'd' or c == 'n' then
|
||||
local f = expect(argnum, select(argnum, ...), "number")
|
||||
argnum = argnum + 1
|
||||
local lh, ll = doubleToRawLongBits(f)
|
||||
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 0, alignment - 1 do
|
||||
if pos % math.min(8, alignment) == 0 then break end
|
||||
output[pos] = 0
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
for j = 0, 3 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(ll, (j * 8)), 0xFF) end
|
||||
for j = 4, 7 do output[pos + (endianness == ByteOrder.BIG_ENDIAN and 7 - j or j)] = bit32.band(bit32.rshift(lh, ((j - 4) * 8)), 0xFF) end
|
||||
pos = pos + 8
|
||||
elseif c == 'c' then
|
||||
local size = 0
|
||||
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
|
||||
error("missing size for format option 'c'", 2)
|
||||
end
|
||||
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (pos + size < pos or pos + size > 0xFFFFFFFF) then error("bad argument #1 to 'pack' (format result too large)", 2) end
|
||||
local str = expect(argnum, select(argnum, ...), "string")
|
||||
argnum = argnum + 1
|
||||
if (#str > size) then error(string.format("bad argument #%d to 'pack' (string longer than given size)", argnum - 1), 2) end
|
||||
if size > 0 then
|
||||
for j = 0, size - 1 do output[pos+j] = str:byte(j + 1) or 0 end
|
||||
pos = pos + size
|
||||
end
|
||||
elseif c == 'z' then
|
||||
local str = expect(argnum, select(argnum, ...), "string")
|
||||
argnum = argnum + 1
|
||||
for b in str:gmatch "." do if (b == '\0') then error(string.format("bad argument #%d to 'pack' (string contains zeros)", argnum - 1), 2) end end
|
||||
for j = 0, #str - 1 do output[pos+j] = str:byte(j + 1) end
|
||||
output[pos + #str] = 0
|
||||
pos = pos + #str + 1
|
||||
elseif c == 's' then
|
||||
local size = 0
|
||||
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size == 0) then size = 4 end
|
||||
local str = expect(argnum, select(argnum, ...), "string")
|
||||
argnum = argnum + 1
|
||||
if (#str >= math.pow(2, (size * 8))) then
|
||||
error(string.format("bad argument #%d to 'pack' (string length does not fit in given size)", argnum - 1), 2)
|
||||
end
|
||||
packint(#str, size, output, pos, 1, endianness, false)
|
||||
for j = size, #str + size - 1 do output[pos+j] = str:byte(j - size + 1) or 0 end
|
||||
pos = pos + #str + size
|
||||
elseif c == 'x' then
|
||||
output[pos] = 0
|
||||
pos = pos + 1
|
||||
elseif c == 'X' then
|
||||
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
|
||||
local size = 0
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c:lower() == 'i' then
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16 or size == 0) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
end
|
||||
else size = packoptsize(c, 0) end
|
||||
if (size < 1) then error("invalid next option for option 'X'", 2) end
|
||||
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(size, alignment) == 0 then break end
|
||||
output[pos] = 0
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
|
||||
end
|
||||
return string.char(table.unpack(output))
|
||||
end
|
||||
|
||||
--[[
|
||||
* string.packsize (fmt)
|
||||
*
|
||||
* Returns the size of a string resulting from string.pack with the given format.
|
||||
* The format string cannot have the variable-length options 's' or 'z'.
|
||||
]]
|
||||
local function packsize(fmt)
|
||||
local pos = 0
|
||||
local alignment = 1
|
||||
local i = 1
|
||||
while i <= #fmt do
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c == '!' then
|
||||
local size = 0
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size == 0) then alignment = 4
|
||||
else alignment = size end
|
||||
elseif isint[c] then
|
||||
local size = packoptsize(c, 0)
|
||||
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(size, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
pos = pos + size
|
||||
elseif c:lower() == 'i' then
|
||||
local size = 0
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size))
|
||||
elseif (alignment > 1 and (size ~= 1 and size ~= 2 and size ~= 4 and size ~= 8 and size ~= 16)) then
|
||||
error("bad argument #1 to 'pack' (format asks for alignment not power of 2)", 2)
|
||||
elseif (size == 0) then size = 4 end
|
||||
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(size, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
pos = pos + size
|
||||
elseif c == 'f' then
|
||||
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(4, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
pos = pos + 4
|
||||
elseif c == 'd' or c == 'n' then
|
||||
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(8, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
pos = pos + 8
|
||||
elseif c == 'c' then
|
||||
local size = 0
|
||||
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
|
||||
error("missing size for format option 'c'", 2)
|
||||
end
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (pos + size < pos or pos + size > 0x7FFFFFFF) then error("bad argument #1 to 'packsize' (format result too large)", 2) end
|
||||
pos = pos + size
|
||||
elseif c == 'x' then
|
||||
pos = pos + 1
|
||||
elseif c == 'X' then
|
||||
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
|
||||
local size = 0
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c:lower() == 'i' then
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16 or size == 0) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
end
|
||||
else size = packoptsize(c, 0) end
|
||||
if (size < 1) then error("invalid next option for option 'X'", 2) end
|
||||
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(size, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
elseif c == 's' or c == 'z' then error("bad argument #1 to 'packsize' (variable-length format)", 2)
|
||||
elseif c ~= ' ' and c ~= '<' and c ~= '>' and c ~= '=' then error(string.format("invalid format option '%s'", c), 2) end
|
||||
end
|
||||
return pos
|
||||
end
|
||||
|
||||
--[[
|
||||
* string.unpack (fmt, s [, pos])
|
||||
*
|
||||
* Returns the values packed in string s (see string.pack) according to the format string fmt.
|
||||
* An optional pos marks where to start reading in s (default is 1).
|
||||
* After the read values, this function also returns the index of the first unread byte in s.
|
||||
]]
|
||||
local function unpack(fmt, str, pos)
|
||||
expect(1, fmt, "string")
|
||||
expect(2, str, "string")
|
||||
expect(3, pos, "number", "nil")
|
||||
if pos then
|
||||
if (pos < 0) then pos = #str + pos
|
||||
elseif (pos == 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
|
||||
if (pos > #str or pos < 0) then error("bad argument #3 to 'unpack' (initial position out of string)", 2) end
|
||||
else pos = 1 end
|
||||
local endianness = ByteOrder.LITTLE_ENDIAN
|
||||
local alignment = 1
|
||||
local retval = {}
|
||||
local i = 1
|
||||
while i <= #fmt do
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c == '<' or c == '=' then
|
||||
endianness = ByteOrder.LITTLE_ENDIAN
|
||||
elseif c == '>' then
|
||||
endianness = ByteOrder.BIG_ENDIAN
|
||||
elseif c == '!' then
|
||||
local size = 0
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size))
|
||||
elseif (size == 0) then alignment = 4
|
||||
else alignment = size end
|
||||
elseif isint[c] then
|
||||
if (pos + packoptsize(c, 0) > #str + 1) then error("data string too short", 2) end
|
||||
local res, ressz = unpackint(str, pos, packoptsize(c, 0), endianness, alignment, c:match("%l") ~= nil)
|
||||
retval[#retval+1] = res
|
||||
pos = pos + ressz
|
||||
elseif c:lower() == 'i' then
|
||||
local signed = c == 'i'
|
||||
local size = 0
|
||||
while (i <= #fmt and fmt:sub(i, i):match("%d")) do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size > 8) then
|
||||
error(string.format("%d-byte integer does not fit into Lua Integer", size), 2)
|
||||
elseif (size == 0) then size = 4 end
|
||||
if (pos + size > #str + 1) then error("data string too short", 2) end
|
||||
local res, ressz = unpackint(str, pos, size, endianness, alignment, signed)
|
||||
retval[#retval+1] = res
|
||||
pos = pos + ressz
|
||||
elseif c == 'f' then
|
||||
if (pos % math.min(4, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(4, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
if (pos + 4 > #str + 1) then error("data string too short", 2) end
|
||||
local res = unpackint(str, pos, 4, endianness, alignment, false)
|
||||
retval[#retval+1] = intBitsToFloat(res)
|
||||
pos = pos + 4
|
||||
elseif c == 'd' or c == 'n' then
|
||||
if (pos % math.min(8, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(8, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
if (pos + 8 > #str + 1) then error("data string too short", 2) end
|
||||
local lh, ll = 0, 0
|
||||
for j = 0, 3 do lh = bit32.bor(lh, bit32.lshift((str:byte(pos + j)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
|
||||
for j = 0, 3 do ll = bit32.bor(ll, bit32.lshift((str:byte(pos + j + 4)), ((endianness == ByteOrder.BIG_ENDIAN and 3 - j or j) * 8))) end
|
||||
if endianness == ByteOrder.LITTLE_ENDIAN then lh, ll = ll, lh end
|
||||
retval[#retval+1] = longBitsToDouble(lh, ll)
|
||||
pos = pos + 8
|
||||
elseif c == 'c' then
|
||||
local size = 0
|
||||
if (i > #fmt or not fmt:sub(i, i):match("%d")) then
|
||||
error("missing size for format option 'c'", 2)
|
||||
end
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)") end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (pos + size > #str + 1) then error("data string too short", 2) end
|
||||
retval[#retval+1] = str:sub(pos, pos + size - 1)
|
||||
pos = pos + size
|
||||
elseif c == 'z' then
|
||||
local size = 0
|
||||
while (str:byte(pos + size) ~= 0) do
|
||||
size = size + 1
|
||||
if (pos + size > #str) then error("unfinished string for format 'z'", 2) end
|
||||
end
|
||||
retval[#retval+1] = str:sub(pos, pos + size - 1)
|
||||
pos = pos + size + 1
|
||||
elseif c == 's' then
|
||||
local size = 0
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size == 0) then size = 4 end
|
||||
if (pos + size > #str + 1) then error("data string too short", 2) end
|
||||
local num, numsz = unpackint(str, pos, size, endianness, alignment, false)
|
||||
pos = pos + numsz
|
||||
if (pos + num > #str + 1) then error("data string too short", 2) end
|
||||
retval[#retval+1] = str:sub(pos, pos + num - 1)
|
||||
pos = pos + num
|
||||
elseif c == 'x' then
|
||||
pos = pos + 1
|
||||
elseif c == 'X' then
|
||||
if (i >= #fmt) then error("invalid next option for option 'X'", 2) end
|
||||
local size = 0
|
||||
local c = fmt:sub(i, i)
|
||||
i = i + 1
|
||||
if c:lower() == 'i' then
|
||||
while i <= #fmt and fmt:sub(i, i):match("%d") do
|
||||
if (size >= 0xFFFFFFFF / 10) then error("bad argument #1 to 'pack' (invalid format)", 2) end
|
||||
size = (size * 10) + tonumber(fmt:sub(i, i))
|
||||
i = i + 1
|
||||
end
|
||||
if (size > 16 or size == 0) then
|
||||
error(string.format("integral size (%d) out of limits [1,16]", size), 2)
|
||||
elseif (size == -1) then size = 4 end
|
||||
else size = packoptsize(c, 0) end
|
||||
if (size < 1) then error("invalid next option for option 'X'", 2) end
|
||||
if (pos % math.min(size, alignment) ~= 0 and alignment > 1) then
|
||||
for j = 1, alignment do
|
||||
if pos % math.min(size, alignment) == 0 then break end
|
||||
pos = pos + 1
|
||||
end
|
||||
end
|
||||
elseif c ~= ' ' then error(string.format("invalid format option '%s'", c), 2) end
|
||||
end
|
||||
retval[#retval+1] = pos
|
||||
return table.unpack(retval)
|
||||
end
|
||||
|
||||
return {
|
||||
pack = pack,
|
||||
packsize = packsize,
|
||||
unpack = unpack
|
||||
}
|
637
computer/28/lib/youcubeapi.lua
Normal file
637
computer/28/lib/youcubeapi.lua
Normal file
|
@ -0,0 +1,637 @@
|
|||
--[[- Lua library for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
|
||||
@module youcubeapi
|
||||
]]
|
||||
|
||||
--[[ youcubeapi.lua
|
||||
_ _ ____ _ _ ____ _ _ ___ ____ ____ ___ _
|
||||
\_/ | | | | | | | |__] |___ |__| |__] |
|
||||
| |__| |__| |___ |__| |__] |___ | | | |
|
||||
]]
|
||||
|
||||
--[[- "wrapper" for accessing [YouCub's API](https://commandcracker.github.io/YouCube/)
|
||||
@type API
|
||||
@usage Example:
|
||||
|
||||
local youcubeapi = require("youcubeapi")
|
||||
local api = youcubeapi.API.new()
|
||||
api:detect_bestest_server()
|
||||
api:request_media(url)
|
||||
local data = api.websocket.receive()
|
||||
]]
|
||||
local API = {}
|
||||
|
||||
--- Create's a new API instance.
|
||||
-- @param websocket [Websocket](https://tweaked.cc/module/http.html#ty:Websocket) The websocket.
|
||||
-- @treturn API instance
|
||||
function API.new(websocket)
|
||||
return setmetatable({
|
||||
websocket = websocket,
|
||||
}, { __index = API })
|
||||
end
|
||||
|
||||
-- Look at the [Documentation](https://commandcracker.github.io/YouCube/) for moor information
|
||||
-- Contact the server owner on Discord, when the server is down
|
||||
local servers = {
|
||||
"ws://127.0.0.1:5000", -- Your server!
|
||||
"wss://us-ky.youcube.knijn.one", -- By EmmaKnijn
|
||||
"wss://youcube.knijn.one", -- By EmmaKnijn
|
||||
"wss://youcube.onrender.com", -- By Commandcracker#8528
|
||||
}
|
||||
|
||||
if settings then
|
||||
local server = settings.get("youcube.server")
|
||||
if server then
|
||||
table.insert(servers, 1, server)
|
||||
end
|
||||
end
|
||||
|
||||
local function websocket_with_timeout(_url, _headers, _timeout)
|
||||
if http.websocketAsync then
|
||||
local websocket, websocket_error = http.websocketAsync(_url, _headers)
|
||||
if not websocket then
|
||||
return false, websocket_error
|
||||
end
|
||||
|
||||
local timerID = os.startTimer(_timeout)
|
||||
|
||||
while true do
|
||||
local event, param1, param2 = os.pullEvent()
|
||||
|
||||
-- TODO: Close web-socket when the connection succeeds after the timeout
|
||||
if event == "websocket_success" and param1 == _url then
|
||||
return param2
|
||||
elseif event == "websocket_failure" and param1 == _url then
|
||||
return false, param2
|
||||
elseif event == "timer" and param1 == timerID then
|
||||
return false, "Timeout"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- use websocket without timeout
|
||||
-- when the CC version dos not support websocketAsync
|
||||
return http.websocket(_url, _headers)
|
||||
end
|
||||
|
||||
--- Connects to a YouCub Server
|
||||
function API:detect_bestest_server(_server, _verbose)
|
||||
if _server then
|
||||
table.insert(servers, 1, _server)
|
||||
end
|
||||
|
||||
for i = 1, #servers do
|
||||
local server = servers[i]
|
||||
local ok, err = http.checkURL(server:gsub("^ws://", "http://"):gsub("^wss://", "https://"))
|
||||
|
||||
if ok then
|
||||
if _verbose then
|
||||
print("Trying to connect to:", server)
|
||||
end
|
||||
local websocket, websocket_error = websocket_with_timeout(server, nil, 5)
|
||||
|
||||
if websocket ~= false then
|
||||
term.write("Using the YouCube server: ")
|
||||
term.setTextColor(colors.blue)
|
||||
print(server)
|
||||
term.setTextColor(colors.white)
|
||||
self.websocket = websocket
|
||||
break
|
||||
elseif i == #servers then
|
||||
error(websocket_error)
|
||||
elseif _verbose then
|
||||
print(websocket_error)
|
||||
end
|
||||
elseif i == #servers then
|
||||
error(err)
|
||||
elseif _verbose then
|
||||
print(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--- Receive data from The YouCub Server
|
||||
-- @tparam string filter action filter
|
||||
-- @treturn table retval data
|
||||
function API:receive(filter)
|
||||
local status, retval = pcall(self.websocket.receive)
|
||||
if not status then
|
||||
error("Lost connection to server\n" .. retval)
|
||||
end
|
||||
|
||||
if retval == nil then
|
||||
error("Received empty message or max message size exceeded")
|
||||
end
|
||||
|
||||
local data, err = textutils.unserialiseJSON(retval)
|
||||
|
||||
if data == nil then
|
||||
error("Failed to parse message\n" .. err)
|
||||
end
|
||||
|
||||
if filter then
|
||||
--if type(filter) == "table" then
|
||||
-- if not filter[data.action] then
|
||||
-- return self:receive(filter)
|
||||
-- end
|
||||
--else
|
||||
if data.action ~= filter then
|
||||
return self:receive(filter)
|
||||
end
|
||||
end
|
||||
|
||||
return data
|
||||
end
|
||||
|
||||
--- Send data to The YouCub Server
|
||||
-- @tparam table data data to send
|
||||
function API:send(data)
|
||||
local status, retval = pcall(self.websocket.send, textutils.serialiseJSON(data))
|
||||
if not status then
|
||||
error("Lost connection to server\n" .. retval)
|
||||
end
|
||||
end
|
||||
|
||||
--[[- [Base64](https://wikipedia.org/wiki/Base64) functions
|
||||
@type Base64
|
||||
]]
|
||||
local Base64 = {}
|
||||
|
||||
local b64str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
|
||||
|
||||
-- based on https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua
|
||||
--- Decode base64 string
|
||||
-- @tparam string str base64 string
|
||||
-- @treturn string string decoded string
|
||||
function Base64.decode(str)
|
||||
local retval = ""
|
||||
for s in str:gmatch("....") do
|
||||
if s:sub(3, 4) == "==" then
|
||||
retval = retval
|
||||
.. string.char(
|
||||
bit32.bor(
|
||||
bit32.lshift(b64str:find(s:sub(1, 1)) - 1, 2),
|
||||
bit32.rshift(b64str:find(s:sub(2, 2)) - 1, 4)
|
||||
)
|
||||
)
|
||||
elseif s:sub(4, 4) == "=" then
|
||||
local n = (b64str:find(s:sub(1, 1)) - 1) * 4096
|
||||
+ (b64str:find(s:sub(2, 2)) - 1) * 64
|
||||
+ (b64str:find(s:sub(3, 3)) - 1)
|
||||
retval = retval .. string.char(bit32.extract(n, 10, 8)) .. string.char(bit32.extract(n, 2, 8))
|
||||
else
|
||||
local n = (b64str:find(s:sub(1, 1)) - 1) * 262144
|
||||
+ (b64str:find(s:sub(2, 2)) - 1) * 4096
|
||||
+ (b64str:find(s:sub(3, 3)) - 1) * 64
|
||||
+ (b64str:find(s:sub(4, 4)) - 1)
|
||||
retval = retval
|
||||
.. string.char(bit32.extract(n, 16, 8))
|
||||
.. string.char(bit32.extract(n, 8, 8))
|
||||
.. string.char(bit32.extract(n, 0, 8))
|
||||
end
|
||||
end
|
||||
return retval
|
||||
end
|
||||
|
||||
--- Request a `16 * 1024` bit chunk
|
||||
-- @tparam number chunkindex The chunkindex
|
||||
-- @tparam string id Media id
|
||||
-- @treturn bytes chunk `16 * 1024` bit chunk
|
||||
function API:get_chunk(chunkindex, id)
|
||||
self:send({
|
||||
["action"] = "get_chunk",
|
||||
["chunkindex"] = chunkindex,
|
||||
["id"] = id,
|
||||
})
|
||||
return Base64.decode(self:receive("chunk").chunk)
|
||||
end
|
||||
|
||||
--- Get 32vid
|
||||
-- @tparam number line The line to return
|
||||
-- @tparam string id Media id
|
||||
-- @tparam number width Video width
|
||||
-- @tparam number height Video height
|
||||
-- @treturn string line one line of the given 32vid
|
||||
function API:get_vid(tracker, id, width, height)
|
||||
self:send({
|
||||
["action"] = "get_vid",
|
||||
["tracker"] = tracker,
|
||||
["id"] = id,
|
||||
["width"] = width * 2,
|
||||
["height"] = height * 3,
|
||||
})
|
||||
return self:receive("vid")
|
||||
end
|
||||
|
||||
--- Request media
|
||||
-- @tparam string url Url or Search Term
|
||||
--@treturn table json response
|
||||
function API:request_media(url, width, height)
|
||||
local request = {
|
||||
["action"] = "request_media",
|
||||
["url"] = url,
|
||||
}
|
||||
if width and height then
|
||||
request.width = width * 2
|
||||
request.height = height * 3
|
||||
end
|
||||
self:send(request)
|
||||
--return self:receive({ ["media"] = true, ["status"] = true })
|
||||
end
|
||||
|
||||
--- Handshake - get Server capabilities and version
|
||||
--@treturn table json response
|
||||
function API:handshake()
|
||||
self:send({
|
||||
["action"] = "handshake",
|
||||
})
|
||||
return self:receive("handshake")
|
||||
end
|
||||
|
||||
--[[- Abstraction for Audio Devices
|
||||
@type AudioDevice
|
||||
]]
|
||||
local AudioDevice = {}
|
||||
|
||||
--- Create's a new AudioDevice instance.
|
||||
-- @tparam table object Base values
|
||||
-- @treturn AudioDevice instance
|
||||
function AudioDevice.new(object)
|
||||
-- @type AudioDevice
|
||||
local self = object or {}
|
||||
|
||||
function self:validate() end
|
||||
|
||||
function self:setLabel(lable) end
|
||||
|
||||
function self:write(chunk) end
|
||||
|
||||
function self:play() end
|
||||
|
||||
function self:reset() end
|
||||
|
||||
function self:setVolume(volume) end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- @{AudioDevice} from a Speaker
|
||||
@type Speaker
|
||||
@usage Example:
|
||||
|
||||
local youcubeapi = require("youcubeapi")
|
||||
local speaker = peripheral.find("speaker")
|
||||
local audiodevice = youcubeapi.Speaker.new(speaker)
|
||||
]]
|
||||
local Speaker = {}
|
||||
|
||||
local decoder
|
||||
local status, dfpwm = pcall(require, "cc.audio.dfpwm")
|
||||
|
||||
if status then
|
||||
decoder = dfpwm.make_decoder()
|
||||
end
|
||||
|
||||
--- Create's a new Tape instance.
|
||||
-- @tparam speaker speaker The speaker
|
||||
-- @treturn AudioDevice|Speaker instance
|
||||
function Speaker.new(speaker)
|
||||
local self = AudioDevice.new({ speaker = speaker })
|
||||
|
||||
function self:validate()
|
||||
if not decoder then
|
||||
return "This ComputerCraft version dos not support DFPWM"
|
||||
end
|
||||
end
|
||||
|
||||
function self:setVolume(volume)
|
||||
self.volume = volume
|
||||
end
|
||||
|
||||
function self:write(chunk)
|
||||
local buffer = decoder(chunk)
|
||||
while not self.speaker.playAudio(buffer, self.volume) do
|
||||
os.pullEvent("speaker_audio_empty")
|
||||
end
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- @{AudioDevice} from a [Computronics tape_drive](https://wiki.vexatos.com/wiki:computronics:tape)
|
||||
@type Tape
|
||||
@usage Example:
|
||||
|
||||
local youcubeapi = require("youcubeapi")
|
||||
local tape_drive = peripheral.find("tape_drive")
|
||||
local audiodevice = youcubeapi.Tape.new(tape_drive)
|
||||
]]
|
||||
local Tape = {}
|
||||
|
||||
--- Create's a new Tape instance.
|
||||
-- @tparam tape tape The tape_drive
|
||||
-- @treturn AudioDevice|Tape instance
|
||||
function Tape.new(tape)
|
||||
local self = AudioDevice.new({ tape = tape })
|
||||
|
||||
function self:validate()
|
||||
if not self.tape.isReady() then
|
||||
return "You need to insert a tape"
|
||||
end
|
||||
end
|
||||
|
||||
function self:setVolume(volume)
|
||||
if volume then
|
||||
self.tape.setVolume(volume)
|
||||
end
|
||||
end
|
||||
|
||||
function self:play(chunk)
|
||||
self.tape.seek(-self.tape.getSize())
|
||||
self.tape.play()
|
||||
end
|
||||
|
||||
function self:write(chunk)
|
||||
self.tape.write(chunk)
|
||||
end
|
||||
|
||||
function self:setLabel(lable)
|
||||
self.tape.setLabel(lable)
|
||||
end
|
||||
|
||||
function self:reset()
|
||||
-- based on https://github.com/Vexatos/Computronics/blob/b0ade53cab10529dbe91ebabfa882d1b4b21fa90/src/main/resources/assets/computronics/lua/peripheral/tape_drive/programs/tape_drive/tape#L109-L123
|
||||
local size = self.tape.getSize()
|
||||
self.tape.stop()
|
||||
self.tape.seek(-size)
|
||||
self.tape.stop()
|
||||
self.tape.seek(-90000)
|
||||
local s = string.rep(string.char(170), 8192)
|
||||
for i = 1, size + 8191, 8192 do
|
||||
self.tape.write(s)
|
||||
end
|
||||
self.tape.seek(-size)
|
||||
self.tape.seek(-90000)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- Abstract object for filling a @{Buffer}
|
||||
@type Filler
|
||||
]]
|
||||
local Filler = {}
|
||||
|
||||
--- Create's a new Filler instance.
|
||||
-- @treturn Filler instance
|
||||
function Filler.new()
|
||||
local self = {}
|
||||
function self:next() end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- @{Filler} for Audio
|
||||
@type AudioFiller
|
||||
]]
|
||||
local AudioFiller = {}
|
||||
|
||||
--- Create's a new AudioFiller instance.
|
||||
-- @tparam API youcubeapi API object
|
||||
-- @tparam string id Media id
|
||||
-- @treturn AudioFiller|Filler instance
|
||||
function AudioFiller.new(youcubeapi, id)
|
||||
local self = {
|
||||
id = id,
|
||||
chunkindex = 0,
|
||||
youcubeapi = youcubeapi,
|
||||
}
|
||||
|
||||
function self:next()
|
||||
local response = self.youcubeapi:get_chunk(self.chunkindex, self.id)
|
||||
self.chunkindex = self.chunkindex + 1
|
||||
return response
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- @{Filler} for Video
|
||||
@type VideoFiller
|
||||
]]
|
||||
local VideoFiller = {}
|
||||
|
||||
--- Create's a new VideoFiller instance.
|
||||
-- @tparam API youcubeapi API object
|
||||
-- @tparam string id Media id
|
||||
-- @tparam number width Video width
|
||||
-- @tparam number height Video height
|
||||
-- @treturn VideoFiller|Filler instance
|
||||
function VideoFiller.new(youcubeapi, id, width, height)
|
||||
local self = {
|
||||
id = id,
|
||||
width = width,
|
||||
height = height,
|
||||
tracker = 0,
|
||||
youcubeapi = youcubeapi,
|
||||
}
|
||||
|
||||
function self:next()
|
||||
local response = self.youcubeapi:get_vid(self.tracker, self.id, self.width, self.height)
|
||||
for i = 1, #response.lines do
|
||||
self.tracker = self.tracker + #response.lines[i] + 1
|
||||
end
|
||||
return response.lines
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
--[[- Buffers Data
|
||||
@type Buffer
|
||||
]]
|
||||
local Buffer = {}
|
||||
|
||||
--- Create's a new Buffer instance.
|
||||
-- @tparam Filler filler filler instance
|
||||
-- @tparam number size buffer limit
|
||||
-- @treturn Buffer instance
|
||||
function Buffer.new(filler, size)
|
||||
local self = {
|
||||
filler = filler,
|
||||
size = size,
|
||||
}
|
||||
self.buffer = {}
|
||||
|
||||
function self:next()
|
||||
while #self.buffer == 0 do
|
||||
os.pullEvent()
|
||||
end -- Wait until next is available
|
||||
local next = self.buffer[1]
|
||||
table.remove(self.buffer, 1)
|
||||
return next
|
||||
end
|
||||
|
||||
function self:fill()
|
||||
if #self.buffer < self.size then
|
||||
local next = filler:next()
|
||||
if type(next) == "table" then
|
||||
for i = 1, #next do
|
||||
self.buffer[#self.buffer + 1] = next[i]
|
||||
end
|
||||
else
|
||||
self.buffer[#self.buffer + 1] = next
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
local currnt_palette = {}
|
||||
|
||||
for i = 0, 15 do
|
||||
local r, g, b = term.getPaletteColour(2 ^ i)
|
||||
currnt_palette[i] = { r, g, b }
|
||||
end
|
||||
|
||||
local function reset_term()
|
||||
for i = 0, 15 do
|
||||
term.setPaletteColor(2 ^ i, currnt_palette[i][1], currnt_palette[i][2], currnt_palette[i][3])
|
||||
end
|
||||
term.setBackgroundColor(colors.black)
|
||||
term.setTextColor(colors.white)
|
||||
term.clear()
|
||||
term.setCursorPos(1, 1)
|
||||
end
|
||||
|
||||
--[[- Create's a new Buffer instance.
|
||||
|
||||
Based on [sanjuuni/raw-player.lua](https://github.com/MCJack123/sanjuuni/blob/c64f8725a9f24dec656819923457717dfb964515/raw-player.lua)
|
||||
and [sanjuuni/websocket-player.lua](https://github.com/MCJack123/sanjuuni/blob/30dcabb4b56f1eb32c88e1bce384b0898367ebda/websocket-player.lua)
|
||||
@tparam Buffer buffer filled with frames
|
||||
]]
|
||||
local function play_vid(buffer, force_fps, string_unpack)
|
||||
if not string_unpack then
|
||||
string_unpack = string.unpack
|
||||
end
|
||||
local Fwidth, Fheight = term.getSize()
|
||||
local tracker = 0
|
||||
|
||||
if buffer:next() ~= "32Vid 1.1" then
|
||||
error("Unsupported file")
|
||||
end
|
||||
|
||||
local fps = tonumber(buffer:next())
|
||||
if force_fps then
|
||||
fps = force_fps
|
||||
end
|
||||
|
||||
-- Adjust buffer size
|
||||
buffer.size = math.ceil(fps) * 2
|
||||
|
||||
local first, second = buffer:next(), buffer:next()
|
||||
|
||||
if second == "" or second == nil then
|
||||
fps = 0
|
||||
end
|
||||
term.clear()
|
||||
|
||||
local start = os.epoch("utc")
|
||||
local frame_count = 0
|
||||
while true do
|
||||
frame_count = frame_count + 1
|
||||
local frame
|
||||
if first then
|
||||
frame, first = first, nil
|
||||
elseif second then
|
||||
frame, second = second, nil
|
||||
else
|
||||
frame = buffer:next()
|
||||
end
|
||||
if frame == "" or frame == nil then
|
||||
break
|
||||
end
|
||||
local mode = frame:match("^!CP([CD])")
|
||||
if not mode then
|
||||
error("Invalid file")
|
||||
end
|
||||
local b64data
|
||||
if mode == "C" then
|
||||
local len = tonumber(frame:sub(5, 8), 16)
|
||||
b64data = frame:sub(9, len + 8)
|
||||
else
|
||||
local len = tonumber(frame:sub(5, 16), 16)
|
||||
b64data = frame:sub(17, len + 16)
|
||||
end
|
||||
local data = Base64.decode(b64data)
|
||||
-- TODO: maybe verify checksums?
|
||||
assert(data:sub(1, 4) == "\0\0\0\0" and data:sub(9, 16) == "\0\0\0\0\0\0\0\0", "Invalid file")
|
||||
local width, height = string_unpack("HH", data, 5)
|
||||
local c, n, pos = string_unpack("c1B", data, 17)
|
||||
local text = {}
|
||||
for y = 1, height do
|
||||
text[y] = ""
|
||||
for x = 1, width do
|
||||
text[y] = text[y] .. c
|
||||
n = n - 1
|
||||
if n == 0 then
|
||||
c, n, pos = string_unpack("c1B", data, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
c = c:byte()
|
||||
for y = 1, height do
|
||||
local fg, bg = "", ""
|
||||
for x = 1, width do
|
||||
fg, bg = fg .. ("%x"):format(bit32.band(c, 0x0F)), bg .. ("%x"):format(bit32.rshift(c, 4))
|
||||
n = n - 1
|
||||
if n == 0 then
|
||||
c, n, pos = string_unpack("BB", data, pos)
|
||||
end
|
||||
end
|
||||
term.setCursorPos(1, y)
|
||||
term.blit(text[y], fg, bg)
|
||||
end
|
||||
pos = pos - 2
|
||||
local r, g, b
|
||||
for i = 0, 15 do
|
||||
r, g, b, pos = string_unpack("BBB", data, pos)
|
||||
term.setPaletteColor(2 ^ i, r / 255, g / 255, b / 255)
|
||||
end
|
||||
if fps == 0 then
|
||||
read()
|
||||
break
|
||||
else
|
||||
while os.epoch("utc") < start + (frame_count + 1) / fps * 1000 do
|
||||
sleep(1 / fps)
|
||||
end
|
||||
end
|
||||
end
|
||||
reset_term()
|
||||
end
|
||||
|
||||
return {
|
||||
--- "Metadata" - [YouCube API](https://commandcracker.github.io/YouCube/) Version
|
||||
_API_VERSION = "0.0.0-poc.1.0.0",
|
||||
--- "Metadata" - Library Version
|
||||
_VERSION = "0.0.0-poc.1.4.2",
|
||||
--- "Metadata" - Description
|
||||
_DESCRIPTION = "Library for accessing YouCub's API",
|
||||
--- "Metadata" - Homepage / Url
|
||||
_URL = "https://github.com/Commandcracker/YouCube",
|
||||
--- "Metadata" - License
|
||||
_LICENSE = "GPL-3.0",
|
||||
API = API,
|
||||
AudioDevice = AudioDevice,
|
||||
Speaker = Speaker,
|
||||
Tape = Tape,
|
||||
Base64 = Base64,
|
||||
Filler = Filler,
|
||||
AudioFiller = AudioFiller,
|
||||
VideoFiller = VideoFiller,
|
||||
Buffer = Buffer,
|
||||
play_vid = play_vid,
|
||||
reset_term = reset_term,
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue