Jump to content
Wikipedia The Free Encyclopedia

Module:IPblock/sandbox

From Wikipedia, the free encyclopedia
This is the module sandbox page for Module:IPblock (diff).
Module documentationcreate purge
You might want to create a documentation page for this Scribunto module.
Editors can experiment in this module's sandbox (edit | diff) and testcases (create) pages.
Add categories to the /doc subpage. Subpages of this module.
 -- Calculate the minimum-sized blocks of IP addresses that cover each
 -- IPv4 or IPv6 address entered in the arguments.

 localbit32=require('bit32')

 localCollection-- a table to hold items
 Collection={
 add=function(self,item)
 ifitem~=nilthen
 self.n=self.n+1
 self[self.n]=item
 end
 end,
 join=function(self,sep)
 returntable.concat(self,sep)
 end,
 remove=function(self,pos)
 ifself.n>0and(pos==nilor(0<posandpos<=self.n))then
 self.n=self.n-1
 returntable.remove(self,pos)
 end
 end,
 sort=function(self,comp)
 table.sort(self,comp)
 end,
 new=function()
 returnsetmetatable({n=0},Collection)
 end
 }
 Collection.__index=Collection

 localfunctionempty(text)
 -- Return true if text is nil or empty (assuming a string).
 returntext==nilortext==''
 end

 localtimestamps={}-- cache
 localfunctionstart_date(code,months)
 -- Return a timestamp string for a URL to list user contributions
 -- on and after the returned date.
 -- The code specifies the wanted format.
 -- For this module, only recent contributions are wanted, so the
 -- timestamp is today's date less the given number of months (1 to 12).
 localkey=code..months
 ifnottimestamps[key]then
 localdate=os.date('!*t')-- today's UTC date
 localy,m,d=date.year,date.month,date.day-- full year, month (1-12), day (1-31)
 m=m-months
 ifm<=0then
 m=m+12
 y=y-1
 end
 locallimit=m==2and28or30
 ifd>limitthen
 d=limit-- good enough to ensure date is valid
 end
 timestamps['y-m-d'..months]=string.format('%d-%02d-%02d',y,m,d)
 timestamps['ymdHMS'..months]=string.format('%d%02d%02d000000',y,m,d)
 end
 returntimestamps[key]or''
 end

 localnote_text={
 range='*Links for ranges show the contributions in the previous %s.',
 gadget=[=[
 *<span id="need-gadget"></span>Contributions links for IPv6 ranges need the "<span style="color:green;">Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions forms</span>" gadget enabled in [[Special:Preferences#mw-prefsection-gadgets|Special:Preferences]], and scripting enabled in the browser.]=],
 }

 localfunctionmake_note(strings,key)
 -- Record the fact that a particular note is needed, and return
 -- wikitext for a link to the note or '' if no link needed.
 ifnotstrings.nonotethen
 strings.notes=strings.notesor{}
 ifnotstrings.notes[key]then
 ifkey=='gadget'then
 strings.notes[key]=note_text[key]
 elseifkey=='range'then
 localwhen='month'
 ifstrings.months>1then
 when=strings.months..' months'
 end
 strings.notes[key]=string.format(note_text.range,when)
 else
 error('make_note: unexpected key')
 end
 end
 ifkey=='gadget'then
 return' [[#need-gadget|<sup>[note]</sup>]]'
 end
 end
 return''
 end

 localfunctiondescribe_total(total,isalloc)
 -- Return text describing given number of addresses or /64 allocations.
 iftotal<=9999then
 -- Can have fractions if total is the number of /64 allocations.
 iftotal<9then
 return(string.format('%.1f',total):gsub('%.0$',''))
 end
 returnstring.format('%.0f',total)
 end
 ifnotisallocthen
 localalloc=2^64
 iftotal>=allocthen
 returndescribe_total(total/alloc,true)..' /64'
 end
 end
 total=total/1024
 localsuffix='K'
 iftotal>=1024then
 total=total/1024
 suffix='M'
 iftotal>=1024then
 total=total/1024
 suffix='G'
 iftotal>64then
 return'>64G'
 end
 end
 end
 returnstring.format('%.0f',total)..suffix
 end

 localfunctiondescribe_size(ipsize,size)
 -- Return text describing how many IPs are in a range with size = prefix length.
 localfunctionnumtext(n)
 ifn<=16then
 returntostring(2^n)
 end
 ifn<=19then
 returntostring(2^(n-10))..'K'
 end
 ifn<=29then
 returntostring(2^(n-20))..'M'
 end
 ifn<=36then
 returntostring(2^(n-30))..'G'
 end
 return'>64G'
 end
 localhost=ipsize-size
 ifhost<=32then
 -- IPv4 or IPv6.
 returnnumtext(host)
 end
 -- Must be IPv6.
 ifhost<=64then
 locals=({
 [64]='1',[63]='50%',[62]='25%',[61]='12%',
 [60]='6%',[59]='3%',[58]='2%'
 })[host]or'<1%'
 returns..' /64'
 end
 -- IPv6 with size < 64.
 returnnumtext(host-64)..' /64'
 end

 localfunctionipv6_string(ip)
 -- Return a string equivalent to the given IPv6 address.
 localz1,z2-- indices of run of zeros to be displayed as "::"
 localzstart,zcount
 fori=1,9do
 -- Find left-most occurrence of longest run of two or more zeros.
 ifi<9andip[i]==0then
 ifzstartthen
 zcount=zcount+1
 else
 zstart=i
 zcount=1
 end
 else
 ifzcountandzcount>1then
 ifnotz1orzcount>z2-z1+1then
 z1=zstart
 z2=zstart+zcount-1
 end
 end
 zstart=nil
 zcount=nil
 end
 end
 localparts=Collection.new()
 fori=1,8do
 ifz1andz1<=iandi<=z2then
 ifi==z1then
 ifz1==1orz2==8then
 ifz1==1andz2==8then
 return'::'
 end
 parts:add(':')
 else
 parts:add('')
 end
 end
 else
 parts:add(string.format('%x',ip[i]))
 end
 end
 returnparts:join(':')
 end

 localfunctionip_string(ip)
 -- Return a string equivalent to given IP address (IPv4 or IPv6).
 ifip.n==2then
 -- IPv4.
 localparts={}
 fori=1,2do
 localw=ip[i]
 localq=i==1and1or3
 parts[q]=math.floor(w/256)
 parts[q+1]=w%256
 end
 returntable.concat(parts,'.')
 end
 returnipv6_string(ip)
 end

 -- Metatable for some operations on IP addresses.
 localipmt={
 __eq=function(lhs,rhs)
 -- Return true if values in numbered tables match.
 iflhs.n==rhs.nthen
 fori=1,lhs.ndo
 iflhs[i]~=rhs[i]then
 returnfalse
 end
 end
 returntrue
 end
 returnfalse
 end,
 __lt=function(lhs,rhs)
 -- Return true if lhs < rhs; for sort.
 iflhs.n==rhs.nthen
 fori=1,lhs.ndo
 iflhs[i]~=rhs[i]then
 returnlhs[i]<rhs[i]
 end
 end
 returnfalse
 end
 returnlhs.n<rhs.n-- sort IPv4 before IPv6, although not needed
 end,
 }

 localfunctionipv4_address(ip_str)
 -- Return a collection of two 16-bit words (numbers) equivalent
 -- to the IPv4 address given as a quad-dotted string, or
 -- return nil if invalid.
 -- This representation is for compatibility with IPv6 addresses.
 localparts=Collection.new()
 locals=ip_str:match('^%s*(.-)%s*$')..'.'
 foritemins:gmatch('(.-)%.')do
 parts:add(item)
 end
 ifparts.n==4then
 fori,sinipairs(parts)do
 ifs:match('^%d+$')then
 localnum=tonumber(s)
 if0<=numandnum<=255then
 ifnum>0ands:match('^0')then
 -- A redundant leading zero is an error because it is for an IP in octal.
 returnnil
 end
 parts[i]=num
 else
 returnnil
 end
 else
 returnnil
 end
 end
 localresult=Collection.new()
 fori=1,3,2do
 result:add(parts[i]*256+parts[i+1])
 end
 returnsetmetatable(result,ipmt)
 end
 returnnil
 end

 localfunctionipv6_address(ip_str)
 -- Return a collection of eight 16-bit words (numbers) equivalent
 -- to the IPv6 address given as a colon-delimited string, or
 -- return nil if invalid.
 local_,n=ip_str:gsub(':',':')
 ifn<7then
 ip_str,n=ip_str:gsub('::',string.rep(':',9-n))
 end
 localparts=Collection.new()
 foritemin(ip_str..':'):gmatch('(.-):')do
 parts:add(item)
 end
 ifparts.n==8then
 fori,sinipairs(parts)do
 ifs==''then
 parts[i]=0
 else
 localnum=tonumber('0x'..s)
 ifnumand0<=numandnum<=65535then
 parts[i]=num
 else
 returnnil
 end
 end
 end
 returnsetmetatable(parts,ipmt)
 end
 returnnil
 end

 localfunctioncommon_length(num1,num2,nr_bits)
 -- Return number of prefix bits that two integers have in common.
 -- Number of bits in each number is nr_bits = 16, 8, 4, 2 or 1.
 ifnr_bits<=1then
 returnnum1==num2and1or0
 end
 localhalf=nr_bits/2
 localsplitter=2^half
 localupper1,lower1=math.modf(num1/splitter)
 localupper2,lower2=math.modf(num2/splitter)
 ifupper1==upper2then
 lower1=math.floor(lower1*splitter+0.5)
 lower2=math.floor(lower2*splitter+0.5)
 returnhalf+common_length(lower1,lower2,half)
 end
 returncommon_length(upper1,upper2,half)
 end

 localfunctioncommon_prefix_length(ip1,ip2)
 -- Return number of prefix bits that two IPs have in common.
 -- Caller ensures that both IPs are IPv4 or both are IPv6.
 localsize=0
 fori=1,ip1.ndo
 localw1,w2=ip1[i],ip2[i]
 ifw1==w2then
 size=size+16
 else
 returnsize+common_length(w1,w2,16)
 end
 end
 returnsize
 end

 localfunctionip_prefix(ip,length)
 -- Return a copy of ip masked to contain only the prefix of given length.
 localresult={n=ip.n}
 fori=1,ip.ndo
 iflength>0then
 iflength>=16then
 result[i]=ip[i]
 length=length-16
 else
 result[i]=bit32.band(ip[i],bit32.arshift(0xffff8000,length-1))
 length=0
 end
 else
 result[i]=0
 end
 end
 returnsetmetatable(result,ipmt)
 end

 localfunctionip_incremented(ip)
 -- Return a new IP equal to ip + 1.
 -- Will wraparound (255.255.255.255 + 1 = 0.0.0.0)!
 localresult={n=ip.n}
 localcarry=1
 fori=ip.n,1,-1do
 localsum=ip[i]+carry
 ifsum>=0x10000then
 carry=1
 sum=sum-0x10000
 else
 carry=0
 end
 result[i]=sum
 end
 returnsetmetatable(result,ipmt)
 end

 localfunctionis_next_ip(ip1,ip2)
 -- Return true if ip2 is the next IP after ip1 (ip2 == ip1 + 1).
 -- IPs are sorted and unique so ip1 < ip2 and can ignore wrapping to zero.
 -- This is lower overhead than making a new incremented IP then comparing.
 ifip1andip2then
 localcarry=1
 fori=ip1.n,1,-1do
 localsum=ip1[i]+carry
 ifsum>=0x10000then
 carry=1
 sum=sum-0x10000
 else
 carry=0
 end
 ifsum~=ip2[i]then
 returnfalse
 end
 end
 returntrue
 end
 end

 -- Each IP in a range except for the last IP has a 'common' field which is
 -- a number specifying how many bits are common between the prefixes of this
 -- IP and the next IP (0 if this IP starts with 0 and the next starts with 1).
 -- Each non-empty range has exactly one "minimum common", that is, its value
 -- of common is smaller than all others. That there is only one minimum common
 -- follows from the fact that the IPs are unique and sorted.
 localfunctionmake_range(iplist,ifirst,ilast)
 -- Return a table for the range of IPs from iplist[ifirst] to iplist[ilast] inclusive.
 localimin,vmin,done
 ififirst<ilastthen
 fori=ifirst,ilast-1do
 -- Find the (unique) minimum of common lengths.
 localcommon=iplist[i].common
 ifvminthen
 ifvmin>commonthen
 vmin=common
 imin=i
 end
 else
 vmin=common
 imin=i
 end
 end
 else
 vmin=iplist.ipsize
 imin=ifirst
 done=true
 end
 ifvmin>iplist.allocationthen
 -- For IPv6, the default allocation is /64 and there is no point having
 -- more precise ranges as they add unnecessary complexity.
 -- However, using results=all sets allocation = 128 so vmin is not changed.
 vmin=iplist.allocation
 end
 return{
 ifirst=ifirst,-- index of first IP
 ilast=ilast,-- index of last IP
 imin=imin,-- index of IP with minimum common
 size=vmin,-- number of common bits in prefix (the minimum)
 prefix=ip_prefix(iplist[imin],vmin),-- IP table of the base IP
 done=done,-- true if know that this range cannot be improved
 }
 end

 localfunctionsplit_range(iplist,range,depth)
 -- Return a table of two or more ranges that more precisely target
 -- the IPs in range, or return nothing if unable to improve range.
 depth=depthanddepth+1or0
 ifdepth<=20and-- 20 examines 1M contiguous addresses down to individual IPs
 notrange.doneand
 range.size<iplist.allocationand
 range.ifirst<range.ilastthen
 localimin=range.imin
 assert(iminandrange.ifirst<=iminandimin<range.ilast)
 localr1=make_range(iplist,range.ifirst,range.imin)
 localr2=make_range(iplist,range.imin+1,range.ilast)
 localpointless=range.size+1
 ifr1.size>pointlessorr2.size>pointlessthen
 return{r1,r2}
 end
 localresult=Collection.new()
 localfunctionstore_split(range)
 localsplit=split_range(iplist,range,depth)
 ifsplitthen
 for_,rinipairs(split)do
 result:add(r)
 end
 returntrue
 else
 result:add(range)
 end
 end
 localimproved1=store_split(r1)
 localimproved2=store_split(r2)
 ifimproved1orimproved2then
 returnresult
 end
 end
 range.done=true
 end

 localfunctionbetter_summary(iplist,summary)
 -- Return a better summary that more precisely targets the specified IPs,
 -- or return nil if unable to improve the summary.
 localbetter=Collection.new()
 localimproved
 for_,rangeinipairs(summary)do
 localsplit=split_range(iplist,range)
 ifsplitthen
 improved=true
 for_,rinipairs(split)do
 better:add(r)
 end
 else
 better:add(range)
 end
 end
 returnimprovedandbetter
 end

 localfunctionmake_summaries(iplist)
 -- Return a collection where each item is a summary.
 -- A summary is a table of one or more ranges.
 -- A summary covers all the given IPs and probably more.
 -- A range is a table representing a CIDR block such as 1.2.248.0/21.
 -- The first summary found is a single range; each subsequent summary
 -- (if any) uses more ranges to better target the given IPs.
 -- The result omits any summary with a range size that is too small (too many IPs).
 localfunctiongood_size(summary)
 for_,rangeinipairs(summary)do
 ifrange.size<iplist.minsizethen
 returnfalse
 end
 end
 returntrue
 end
 localsummaries=Collection.new()
 ifiplist.n>0then
 fori=1,iplist.n-1do
 -- Set length of prefixes common between each pair of IPs.
 iplist[i].common=common_prefix_length(iplist[i],iplist[i+1])
 end
 localsummary={make_range(iplist,1,iplist.n)}
 whilesummaryandsummaries.n<iplist.maxresultsdo
 ifgood_size(summary)then
 summaries:add(summary)
 end
 summary=better_summary(iplist,summary)
 end
 end
 returnsummaries
 end

 localfunctionextract_ipv4(result,omitted,line)
 -- Extract any IPv4 addresses from given line or throw error.
 -- Accept CIDR /n to specify a range (only accept 16 to 32).
 -- Addresses must be delimited with whitespace to reduce false positives.
 localfunctionstore(hit)
 localn=32
 locallhs,rhs=hit:match('^(.-)/(%d+)$')
 iflhsthen
 hit=lhs
 n=tonumber(rhs)
 ifnot(nand16<=nandn<=32)then
 error('CIDR /n only accepts n = 16 to 32, invalid: '..lhs..'/'..rhs,0)
 end
 end
 localip=ipv4_address(hit)
 ifipthen
 ifn==32then
 result:add(ip)
 else
 ifip~=ip_prefix(ip,n)then
 error('Invalid base address (host bits should be zero): '..hit,0)
 end
 for_=1,2^(32-n)do
 result:add(ip)
 ip=ip_incremented(ip)
 end
 end
 else
 omitted:add(hit)
 end
 end
 line=line:gsub(':',' ')-- so wikitext indents or other colons don't obscure an IVp4 address
 forhitinline:gmatch('%S+')do
 ifhit:match('^%d+%.%d+[%.%d/]+$')then
 local_,n=hit:gsub('%.','.')
 ifn>=3then
 store(hit)
 end
 end
 end
 end

 localfunctionextract_ipv6(result,omitted,line)
 -- Extract any IPv6 addresses from given line or throw error.
 -- Addresses must be delimited with whitespace to reduce false positives.
 -- Want to accept all valid IPv6 despite the fact that contributors will
 -- not have an address starting with ':'.
 -- Also want to be able to parse arbitrary wikitext which might use colons
 -- for indenting. To achieve that, if an address at the start of a line
 -- is valid, use it; otherwise strip any leading colons and try again.
 forpos,hitinline:gmatch('()(%S+)')do
 localipstr,length=hit:match('^([:%x]+)(/?%d*)$')
 ifipstrthen
 local_,n=ipstr:gsub(':',':')
 ifn>=2then
 localip=ipv6_address(ipstr)
 ifnotipandpos==1then
 ipstr,n=ipstr:gsub('^:+','')
 ifn>0then
 ip=ipv6_address(ipstr)
 end
 end
 ifipthen
 iflengthand#length>0then
 error('CIDR /n not accepted for IPv6: '..hit,0)
 end
 result:add(ip)
 else
 omitted:add(hit)
 end
 end
 end
 end
 end

 localfunctioncontribs(address,strings,ipbase,size)
 -- Return a URL or wikilink to list the contributions for an IP or IP range,
 -- or return an empty string if cannot do anything useful.
 -- The given address is a string of either a single IP or a CIDR range.
 -- If using old system:
 -- For IPv6 CIDR, return a Special:Contributions link using an asterisk
 -- wildcard which should work if the user has enabled the gadget
 -- "Allow /16, /24 and /27 – /32 CIDR ranges on Special:Contributions".
 localencoded,count=address:gsub('/','%%2F')
 ifstrings.want_oldandcount>0then
 make_note(strings,'range')
 ifaddress:find(':',1,true)then
 ifipbaseandsizethen
 localdigits=math.floor(size/4)
 ifdigits<3then
 digits=3
 end
 localwildcard=digits%4==0and':*'or'*'
 localparts={}
 fori=1,8do
 localhex=string.format('%X',ipbase[i])-- must be uppercase
 ifdigits>=4then
 parts[i]=hex
 digits=digits-4
 ifdigits<=0then
 break
 end
 else
 localnz-- number of leading zeros in this group of four digits
 ifhex=='0'then
 nz=4
 else
 nz=4-#hex
 end
 ifdigits<=nzthen
 -- Cannot properly handle this case; have to omit group
 -- because "0" never occurs as the first digit.
 wildcard=':*'
 else
 hex=string.rep('0',nz)..hex-- four digits
 parts[i]=hex:sub(1,digits)
 end
 break
 end
 end
 address=table.concat(parts,':')..wildcard
 localurl='[https://en.wikipedia.org/wiki/Special:Contributions/%s?ucstart=%s c]'
 -- %s = IPv6 prefix address in uppercase with '*' wildcard at end
 -- %s = Start date formatted 'yyyymmdd000000'
 returnstring.format(url,address,start_date('ymdHMS',strings.months))..make_note(strings,'gadget')
 end
 return''-- no contributions link available
 end
 localurl='[https://tools.wmflabs.org/xtools/rangecontribs/?project=en.wikipedia.org&namespace=all&limit=50&text=%s&begin=%s c]'
 -- %s = IPv4 CIDR range with '/' changed to '%2F'
 -- %s = Start date formatted 'yyyy-mm-dd'
 returnstring.format(url,encoded,start_date('y-m-d',strings.months))
 end
 return'[[Special:Contributions/'..address..'|c]]'
 end

 -- Strings for results using plain text.
 -- The pre tags used are html which do not provide "nowiki",
 -- but that is not required by the text used.
 localplaintext={

 header=[=[
 <pre>
 Total Affected Given Range]=],

 footer='</pre>',

 sumfirst=[=[
 ----------------------------------------------------------
 %s%-12s %-12s %-11d %s%s]=],
 -- %s = empty string (dummy for compatibility)
 -- %s = total affected
 -- %s = affected
 -- %d = given (number of addresses given in input covered by this range)
 -- %s = IP address range
 -- %s = empty string

 sumnext=[=[
  %-12s %-11d %s%s]=],
 -- %s = affected
 -- %d = given
 -- %s = IP address range
 -- %s = empty string

 }

 -- Strings for results using a table in wikitext.
 localwikitable={

 header=[=[
 {| class="wikitable"
 ! Total<br />affected !! Affected<br />addresses !! Given<br />addresses !! Range !! Contribs]=],

 footer='|}',

 sumfirst=[=[
 |- style="background: darkgray; height: 6px;"
 |colspan="5" |
 |- style="vertical-align: top;"
 |rowspan="%s" |%s ||%s ||%d ||%s ||%s]=],
 -- %s = string of number of ranges in summary (number of rows)
 -- %s = total affected
 -- %s = affected
 -- %d = given
 -- %s = IP address range
 -- %s = contributions link

 sumnext=[=[
 |-
 |%s ||%d ||%s ||%s]=],
 -- %s = affected
 -- %d = given
 -- %s = IP address range
 -- %s = contributions link

 }

 localfunctionshow_summary(lines,strings,iplist,summary)
 -- Show the summary by adding table wikitext or plain text to lines.
 localwant_plain=iplist.want_plain
 localtotal=0
 for_,rangeinipairs(summary)do
 -- A number is a double which easily handles 2^128 = 3.4e38.
 total=total+2^(iplist.ipsize-range.size)
 end
 fori,rangeinipairs(summary)do
 localprefix=ip_string(range.prefix)
 localsize=range.size
 localaffected=describe_size(iplist.ipsize,size)
 localgiven=range.ilast-range.ifirst+1
 localaddress
 locallink=''
 ifsize==iplist.ipsizethen
 address=prefix
 ifnotwant_plainthen
 link=contribs(address,strings)
 end
 else
 address=prefix..'/'..size
 ifnotwant_plainthen
 link=contribs(address,strings,range.prefix,size)
 end
 end
 locals
 ifi==1then
 s=string.format(strings.sumfirst,
 want_plainand''ortostring(#summary),
 describe_total(total),
 affected,given,address,link)
 else
 s=string.format(strings.sumnext,
 affected,given,address,link)
 end
 -- Pre tags returned by a module are html tags, not like wikitext <pre>...</pre>.
 lines:add(want_plainandmw.text.nowiki(s)ors)
 end
 end

 localfunctionprocess_ips(lines,iplist,omitted)
 -- Process a list of IP addresses, adding text of results to lines.
 -- The list should contain either all IPv4 addresses, or all IPv6 (not a mixture).
 localseq1,seq2,seqmany
 localfunctionshow_sequence()
 ifseq1andseq2then
 localtext=ip_string(seq1)
 ifseqmanythen
 seqmany=false
 text=text..' – '..ip_string(seq2)
 end
 seq1=nil
 seq2=nil
 localmarkup=text:sub(1,1)==':'and':<nowiki/>'or':'
 lines:add(markup..text)
 end
 end
 localfunctionshow_ip(ip)
 -- Show IP or record it to be included in a "from to" sequence of IPs.
 ifis_next_ip(seq2,ip)then
 seq2=ip
 seqmany=true
 else
 show_sequence()
 seq1=ip
 seq2=ip
 seqmany=false
 end
 end
 ifiplist.n<1then
 return
 end
 iflines.n>0then
 lines:add('')
 end
 ifomitted.n>0then
 lines:add('Warning, omitted as invalid: '..omitted:join(' '))
 lines:add('')
 end
 lines:add('')-- this blank line is replaced with a heading
 localheading_line=lines.n
 localduplicates=Collection.new()
 localprevious
 iplist:sort()
 -- Check for duplicates which can interfere with method to get ranges.
 fori,ipinipairs(iplist)do
 ifprevious==ipthen
 duplicates:add(i)-- index to omit duplicate later
 elseifnotiplist.nolistthen
 show_ip(ip)
 end
 previous=ip
 end
 show_sequence()
 localduplicate_text=''
 ifduplicates.n>0then
 duplicate_text=' (after omitting some duplicates)'
 fori=duplicates.n,1,-1do
 iplist:remove(duplicates[i])
 end
 end
 localheading_text=string.format('Sorted %d %s address%s',
 iplist.n,
 iplist.ipname,
 iplist.n==1and''or'es'
 )
 lines[heading_line]=heading_text..duplicate_text..':'
 localstrings=iplist.want_plainandplaintextorwikitable
 strings.notes=nil-- needed when module is kept loaded for multiple tests
 strings.want_old=iplist.want_old
 strings.nonote=iplist.nonote
 strings.months=iplist.months
 lines:add(strings.header)
 localupto=lines.n
 for_,summaryinipairs(make_summaries(iplist))do
 show_summary(lines,strings,iplist,summary)
 end
 lines:add(strings.footer)
 ifupto+1==lines.nthen
 -- Show message in the very unlikely event that no results are found.
 lines:add('----')
 lines:add('No suitable ranges found; use <code>|results=all</code> to see all ranges.')
 end
 ifstrings.notesthen
 lines:add('')
 lines:add("'''Notes'''")
 for_,keyinipairs({'range','gadget'})do
 ifstrings.notes[key]then
 lines:add(strings.notes[key])
 end
 end
 end
 end

 localfunctionmake_options(args)
 -- Return table of options from validated args or throw error.
 localoptions={}
 ifnotempty(args.comment)then
 options.comment=args.comment
 end
 -- Parameter 'months' is only used if 'old' is also used.
 localmonths=math.floor(tonumber(args.months)ortonumber(args.month)or1)
 ifmonths<1then
 months=1
 elseifmonths>12then
 months=12
 end
 options.months=months-- silently ignore invalid input
 localallocation
 ifnotempty(args.allocation)then
 allocation=tonumber(args.allocation)
 ifnot(allocationand48<=allocationandallocation<=128)then
 error('Invalid allocation "'..args.allocation..'" (should be 48 to 128; default is 64)',0)
 end
 end
 localmaxresults
 ifnotempty(args.results)then
 ifargs.results=='all'then
 options.all=true
 allocation=allocationor128
 maxresults=1000
 else
 maxresults=tonumber(args.results)
 ifnot(maxresultsand1<=maxresultsandmaxresults<=100)then
 error('Invalid results "'..args.results..'" (should be 1 to 100)',0)
 end
 end
 end
 options.allocation=allocationor64
 options.maxresults=maxresultsor10
 localkeywords={
 -- Table of k, v strings.
 -- If an argument matches k, an option named v is set to true.
 ok='noannounce',
 old='want_old',
 nolist='nolist',
 nonote='nonote',
 text='text',
 }
 localwant_old
 fori,arginipairs(args)do
 localflag=keywords[arg:match('^%s*(%w+)%s*$')]
 ifflagthen
 options[i]='skip'
 options[flag]=true
 ifflag=='want_old'then
 want_old=true
 end
 end
 end
 ifnotwant_oldthen
 options.nonote=true
 end
 returnoptions
 end

 localfunction_IPblock(args)
 -- Process given args; can be called from another module.
 -- Throw an error if need to report a problem.
 localoptions=make_options(args)
 localv4list,v4omitted=Collection.new(),Collection.new()
 localv6list,v6omitted=Collection.new(),Collection.new()
 v4list.ipsize=32
 v4list.ipname='IPv4'
 v6list.ipsize=128
 v6list.ipname='IPv6'
 v4list.allocation=32
 v6list.allocation=options.allocation
 ifoptions.allthen
 v4list.minsize=0
 v6list.minsize=0
 else
 v4list.minsize=16-- cannot block more IPs than /16 for IPv4
 v6list.minsize=19-- or /19 for IPv6 ($wgBlockCIDRLimit)
 end
 for_,kinipairs({'maxresults','months','want_old','nolist','nonote'})do
 v4list[k]=options[k]
 v6list[k]=options[k]
 end
 ifoptions.textthen
 v4list.want_plain=true
 v6list.want_plain=true
 end
 fori,arginipairs(args)do
 ifoptions[i]~='skip'then
 forlineinstring.gmatch(arg..'\n','[\t ]*(.-)[\t\r ]*\n')do
 -- Skip line if is empty or a comment.
 ifline~=''then
 localcomment=options.comment
 ifnot(commentandline:sub(1,#comment)==comment)then
 -- Replace accepted delimiters with a space.
 line=line:gsub('[!"#&\'()+,%-;<=>?[%]_{|}]',' ')
 extract_ipv4(v4list,v4omitted,line)
 extract_ipv6(v6list,v6omitted,line)
 end
 end
 end
 end
 end
 ifv4list.n<1andv6list.n<1then
 error('No valid IPv4 or IPv6 address in arguments',0)
 end
 locallines=Collection.new()
 ifnotoptions.noannouncethen
 -- 1: Commented out April 2016 as expired.
 -- 1: lines:add("'''Please see [[Template talk:Blockcalc#Version February 2016|this announcement]].'''")
 lines:add("'''By default, links now use [[Special:Contributions]] per [[Template talk:IP range calculator#Version November 2017|this announcement]].'''")
 end
 process_ips(lines,v4list,v4omitted)
 process_ips(lines,v6list,v6omitted)
 returnlines:join('\n')
 end

 localfunctionIPblock(frame)
 -- Return wikitext to display the smallest IPv4 or IPv6 CIDR range that
 -- covers each address given in the arguments, or return error text.
 -- Input can have any mixture of IPs; IPv4 and IPv6 are processed separately.
 localok,msg=pcall(_IPblock,frame:getParent().args)
 ifokthen
 returnmsg
 end
 return'<strong class="error">Error: '..msg..'</strong>'
 end

 return{IPblock=IPblock,_IPblock=_IPblock}

AltStyle によって変換されたページ (->オリジナル) /