local fs=require("filesystem") local shell=require("shell") local tty=require("tty") local unicode=require("unicode") local tx=require("transforms") local text=require("text") local dirsArg,ops=shell.parse(...) if ops.help then print([[Usage: ls [OPTION]... [FILE]... -a, --all do not ignore entries starting with . --full-time with -l, print time in full iso format -h, --human-readable with -l and/or -s, print human readable sizes --si likewise, but use powers of 1000 not 1024 -l use a long listing format -r, --reverse reverse order while sorting -R, --recursive list subdirectories recursively -S sort by file size -t sort by modification time, newest first -X sort alphabetically by entry extension -1 list one file per line -p append / indicator to directories -M display Microsoft-style file and directory count after listing --no-color Do not colorize the output (default colorized) --help display this help and exit For more info run: man ls]]) return 0 end if#dirsArg==0 then table.insert(dirsArg,".") end local ec=0 local fOut=tty.isAvailable()and io.output().tty local function perr(msg)io.stderr:write(msg,"\n")ec=2 end local set_color=function()end local function colorize()return end if fOut and not ops["no-color"]then local LSC=tx.foreach(text.split(os.getenv("LS_COLORS")or"",{":"},true),function(e) local parts=text.split(e,{"="},true) return parts[2],parts[1] end) colorize=function(info) return info.isLink and LSC.ln or info.isDir and LSC.di or LSC['*'..info.ext]or LSC.fi end set_color=function(c) io.write(string.char(0x1b),"[",c or"","m") end end local msft={reports=0,proxies={}} function msft.report(files,dirs,used,proxy) local free=proxy.spaceTotal()-proxy.spaceUsed() set_color() local pattern="%5i File(s) %s bytes\n%5i Dir(s) %11s bytes free\n" io.write(string.format(pattern,files,tostring(used),dirs,tostring(free))) end function msft.tail(names) local fsproxy=fs.get(names.path) if not fsproxy then return end local totalSize,totalFiles,totalDirs=0,0,0 for i=1,#names do local info=names[i] if info.isDir then totalDirs=totalDirs+1 else totalFiles=totalFiles+1 end totalSize=totalSize+info.size end msft.report(totalFiles,totalDirs,totalSize,fsproxy) local ps=msft.proxies ps[fsproxy]=ps[fsproxy]or{files=0,dirs=0,used=0} local p=ps[fsproxy] p.files=p.files+totalFiles p.dirs=p.dirs+totalDirs p.used=p.used+totalSize msft.reports=msft.reports+1 end function msft.final() if msft.reports<2 then return end local groups={} for proxy,report in pairs(msft.proxies)do table.insert(groups,{proxy=proxy,report=report}) end set_color() print("Total Files Listed:") for _,pair in ipairs(groups)do local proxy,report=pair.proxy,pair.report if#groups>1 then print("As pertaining to: "..proxy.address) end msft.report(report.files,report.dirs,report.used,proxy) end end if not ops.M then msft.tail=function()end msft.final=function()end end local function nod(n) return n and(tostring(n):gsub("(%.[0-9]+)0+$","%1"))or"0" end local function formatSize(size) if not ops.h and not ops['human-readable']and not ops.si then return tostring(size) end local sizes={"","K","M","G"} local unit=1 local power=ops.si and 1000 or 1024 while size>power and unit<#sizes do unit=unit+1 size=size/power end return nod(math.floor(size*10)/10)..sizes[unit] end local function pad(txt) txt=tostring(txt) return#txt>=2 and txt or"0"..txt end local function formatDate(epochms) local month_names={"January","February","March","April","May","June","July","August","September","October","November","December"} if epochms==0 then return""end local d=os.date("*t",epochms) local day,hour,min,sec=nod(d.day),pad(nod(d.hour)),pad(nod(d.min)),pad(nod(d.sec)) if ops["full-time"]then return string.format("%s-%s-%s %s:%s:%s ",d.year,pad(nod(d.month)),pad(day),hour,min,sec) else return string.format("%s %2s %2s:%2s ",month_names[d.month]:sub(1,3),day,hour,pad(min)) end end local function filter(names) if ops.a then return names end local set={path=names.path} for _,info in ipairs(names)do if fs.name(info.name):sub(1,1)~="."then set[#set+1]=info end end return set end local function sort(names) local once=false local function ni(v) for i=1,#names do local info=names[i] if info.key==v.key then return i end end end local function sorter(key) once=true table.sort(names,function(a,b) local ast=names[ni(a)] local bst=names[ni(b)] return ast[key]>bst[key] end) end if ops.t then sorter("time")end if ops.X then sorter("ext")end if ops.S then sorter("size")end local rev=ops.r or ops.reverse if not once then sorter("sort_name")rev=not rev end if rev then for i=1,#names/2 do names[i],names[#names-i+1]=names[#names-i+1],names[i] end end return names end local function dig(names,dirs,dir) if ops.R then local di=1 for i=1,#names do local info=names[i] if info.isDir then local path=dir..(dir:sub(-1)=="/"and""or"/") table.insert(dirs,di,path..info.name) di=di+1 end end end return names end local first_display=true local function display(names) local mt={} local lines=setmetatable({},mt) if ops.l then lines.n=#names local max_size_width=1 local max_date_width=0 for i=1,lines.n do local info=names[i] max_size_width=math.max(max_size_width,formatSize(info.size):len()) max_date_width=math.max(max_date_width,formatDate(info.time):len()) end mt.__index=function(_,index) local info=names[index] local file_type=info.isLink and'l'or info.isDir and'd'or'f' local link_target=info.isLink and string.format(" -> %s",info.link:gsub("/+$","")..(info.isDir and"/"or""))or"" local write_mode=info.fs.isReadOnly()and'-'or'w' local size=formatSize(info.size) local modDate=formatDate(info.time) local format="%s-r%s %"..tostring(max_size_width).."s %"..tostring(max_date_width).."s" local meta=string.format(format,file_type,write_mode,size,modDate) local item=info.name..link_target return{{name=meta},{color=colorize(info),name=item}} end elseif ops["1"]or not fOut then lines.n=#names mt.__index=function(_,index) local info=names[index] return{{color=colorize(info),name=info.name}} end else local num_columns,items_per_column,width=0,0,tty.getViewport()-1 local function real(x,y) local index=y+((x-1)*items_per_column) return index<=#names and index or nil end local function max_name(column_index) local max=0 for r=1,items_per_column do local ri=real(column_index,r) if not ri then break end local info=names[ri] max=math.max(max,unicode.wlen(info.name)) end return max end local function measure() local total=0 for column_index=1,num_columns do total=total+max_name(column_index)+(column_index>1 and 2 or 0) end return total end while items_per_column<#names do items_per_column=items_per_column+1 num_columns=math.ceil(#names/items_per_column) if measure()1 or ops.R then header=function(path) if not first_display then print()end set_color() io.write(path,":\n") end end local function stat(path,name) local info={} info.key=name info.path=name:sub(1,1)=="/"and""or path info.full_path=fs.concat(info.path,name) info.isDir=fs.isDirectory(info.full_path) info.name=name:gsub("/+$","")..(ops.p and info.isDir and"/"or"") info.sort_name=info.name:gsub("^%.","") info.isLink,info.link=fs.isLink(info.full_path) info.size=info.isLink and 0 or fs.size(info.full_path) info.time=fs.lastModified(info.full_path)/1000 info.fs=fs.get(info.full_path) info.ext=info.name:match("(%.[^.]+)$")or"" return info end local function displayDirList(dirs) while#dirs>0 do local dir=table.remove(dirs,1) header(dir) local path=shell.resolve(dir) local list,reason=fs.list(path) if not list then perr(reason) else local names={path=path} for name in list do names[#names+1]=stat(path,name) end display(dig(sort(filter(names)),dirs,dir)) end end end local dir_set,file_set={},{path=shell.getWorkingDirectory()} for _,dir in ipairs(dirsArg)do local path=shell.resolve(dir) local real,why=fs.realPath(path) local access_msg="cannot access "..tostring(path)..": " if not real then perr(access_msg..why) elseif not fs.exists(path)then perr(access_msg.."No such file or directory") elseif fs.isDirectory(path)then table.insert(dir_set,dir) else table.insert(file_set,stat(dir,dir)) end end io.output():setvbuf("line") local ok,msg=pcall(function() if#file_set>0 then display(sort(file_set))end displayDirList(dir_set) msft.final() end) io.output():flush() io.output():setvbuf("no") assert(ok,msg) return ec