refactor parallel code
This commit is contained in:
323
openos/grep.lua
Normal file
323
openos/grep.lua
Normal file
@@ -0,0 +1,323 @@
|
||||
--[[
|
||||
An adaptation of Wobbo's grep
|
||||
https://raw.githubusercontent.com/OpenPrograms/Wobbo-Programs/master/grep/grep.lua
|
||||
]]--
|
||||
|
||||
-- POSIX grep for OpenComputers
|
||||
-- one difference is that this version uses Lua regex, not POSIX regex.
|
||||
|
||||
local fs = require("openos.filesystem")
|
||||
local shell = require("openos.shell")
|
||||
local tty = require("openos.tty")
|
||||
local computer = require("openos.computer")
|
||||
|
||||
-- Process the command line arguments
|
||||
|
||||
local args, options = shell.parse(...)
|
||||
|
||||
local gpu = tty.gpu()
|
||||
|
||||
local function printUsage(ostream, msg)
|
||||
local s = ostream or io.stdout
|
||||
if msg then
|
||||
s:write(msg,'\n')
|
||||
end
|
||||
s:write([[Usage: grep [OPTION]... PATTERN [FILE]...
|
||||
Example: grep -i "hello world" menu.lua main.lua
|
||||
for more information, run: man grep
|
||||
]])
|
||||
end
|
||||
|
||||
local PATTERNS = {args[1]}
|
||||
local FILES = {select(2, table.unpack(args))}
|
||||
|
||||
local LABEL_COLOR = 0xb000b0
|
||||
local LINE_NUM_COLOR = 0x00FF00
|
||||
local MATCH_COLOR = 0xFF0000
|
||||
local COLON_COLOR = 0x00FFFF
|
||||
|
||||
local function pop(...)
|
||||
local result
|
||||
for _,key in ipairs({...}) do
|
||||
result = options[key] or result
|
||||
options[key] = nil
|
||||
end
|
||||
return result
|
||||
end
|
||||
|
||||
-- Specify the variables for the options
|
||||
local plain = pop('F','fixed-strings')
|
||||
plain = not pop('e','--lua-regexp') and plain
|
||||
local pattern_file = pop('file')
|
||||
local match_whole_word = pop('w','word-regexp')
|
||||
local match_whole_line = pop('x','line-regexp')
|
||||
local ignore_case = pop('i','ignore-case')
|
||||
local stdin_label = pop('label') or '(standard input)'
|
||||
local stderr = pop('s','no-messages') and {write=function()end} or io.stderr
|
||||
local invert_match = not not pop('v','invert-match')
|
||||
|
||||
-- no version output, just help
|
||||
if pop('V','version','help') then
|
||||
printUsage()
|
||||
return 0
|
||||
end
|
||||
|
||||
local max_matches = tonumber(pop('max-count')) or math.huge
|
||||
local print_line_num = pop('n','line-number')
|
||||
local search_recursively = pop('r','recursive')
|
||||
|
||||
-- Table with patterns to check for
|
||||
if pattern_file then
|
||||
local pattern_file_path = shell.resolve(pattern_file)
|
||||
if not fs.exists(pattern_file_path) then
|
||||
stderr:write('grep: ',pattern_file,': file not found')
|
||||
return 2
|
||||
end
|
||||
table.insert(FILES, 1, PATTERNS[1])
|
||||
PATTERNS = {}
|
||||
for line in io.lines(pattern_file_path) do
|
||||
PATTERNS[#PATTERNS+1] = line
|
||||
end
|
||||
end
|
||||
|
||||
if #PATTERNS == 0 then
|
||||
printUsage(stderr)
|
||||
return 2
|
||||
end
|
||||
|
||||
if #FILES == 0 then
|
||||
FILES = search_recursively and {'.'} or {'-'}
|
||||
end
|
||||
|
||||
if not options.h and search_recursively then
|
||||
options.H = true
|
||||
end
|
||||
|
||||
if #FILES < 2 then
|
||||
options.h = true
|
||||
end
|
||||
|
||||
local f_only = pop('l','files-with-matches')
|
||||
local no_only = pop('L','files-without-match') and not f_only
|
||||
|
||||
local include_filename = pop('H','with-filename')
|
||||
include_filename = not pop('h','no-filename') or include_filename
|
||||
|
||||
local m_only = pop('o','only-matching')
|
||||
local quiet = pop('q','quiet','silent')
|
||||
|
||||
local print_count = pop('c','count')
|
||||
local colorize = pop('color','colour') and io.output().tty and tty.isAvailable()
|
||||
|
||||
local noop = function(...)return ...;end
|
||||
local setc = colorize and gpu.setForeground or noop
|
||||
local getc = colorize and gpu.getForeground or noop
|
||||
|
||||
local trim = pop('t','trim')
|
||||
local trim_front = trim and function(s)return s:gsub('^%s+','')end or noop
|
||||
local trim_back = trim and function(s)return s:gsub('%s+$','')end or noop
|
||||
|
||||
if next(options) then
|
||||
if not quiet then
|
||||
printUsage(stderr, 'unexpected option: '..next(options))
|
||||
return 2
|
||||
end
|
||||
return 0
|
||||
end
|
||||
-- Resolve the location of a file, without searching the path
|
||||
local function resolve(file)
|
||||
if file:sub(1,1) == '/' then
|
||||
return fs.canonical(file)
|
||||
else
|
||||
if file:sub(1,2) == './' then
|
||||
file = file:sub(3, -1)
|
||||
end
|
||||
return fs.canonical(fs.concat(shell.getWorkingDirectory(), file))
|
||||
end
|
||||
end
|
||||
|
||||
--- Builds a case insensitive patterns, code from stackoverflow
|
||||
--- (questions/11401890/case-insensitive-lua-pattern-matching)
|
||||
if ignore_case then
|
||||
for i=1,#PATTERNS do
|
||||
-- find an optional '%' (group 1) followed by any character (group 2)
|
||||
PATTERNS[i] = PATTERNS[i]:gsub("(%%?)(.)", function(percent, letter)
|
||||
if percent ~= "" or not letter:match("%a") then
|
||||
-- if the '%' matched, or `letter` is not a letter, return "as is"
|
||||
return percent .. letter
|
||||
else -- case-insensitive
|
||||
return string.format("[%s%s]", letter:lower(), letter:upper())
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
||||
local function getAllFiles(dir, file_list)
|
||||
for node in fs.list(shell.resolve(dir)) do
|
||||
local rel_path = dir:gsub("/+$","") .. '/' .. node
|
||||
local resolved_path = shell.resolve(rel_path)
|
||||
if fs.isDirectory(resolved_path) then
|
||||
getAllFiles(rel_path, file_list)
|
||||
else
|
||||
file_list[#file_list+1] = rel_path
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if search_recursively then
|
||||
local files = {}
|
||||
for _,arg in ipairs(FILES) do
|
||||
if fs.isDirectory(arg) then
|
||||
getAllFiles(arg, files)
|
||||
else
|
||||
files[#files+1]=arg
|
||||
end
|
||||
end
|
||||
FILES=files
|
||||
end
|
||||
|
||||
-- Prepare an iterator for reading files
|
||||
local function readLines()
|
||||
local curHand = nil
|
||||
local curFile = nil
|
||||
local meta = nil
|
||||
return function()
|
||||
if not curFile then
|
||||
local file = table.remove(FILES, 1)
|
||||
if not file then
|
||||
return
|
||||
end
|
||||
meta = {line_num=0,hits=0}
|
||||
if file == "-" then
|
||||
curFile = file
|
||||
meta.label = stdin_label
|
||||
curHand = io.input()
|
||||
else
|
||||
meta.label = file
|
||||
local file, reason = resolve(file)
|
||||
if fs.exists(file) then
|
||||
curHand, reason = io.open(file, 'r')
|
||||
if not curHand then
|
||||
local msg = string.format("failed to read from %s: %s", meta.label, reason)
|
||||
stderr:write("grep: ",msg,"\n")
|
||||
return false, 2
|
||||
else
|
||||
curFile = meta.label
|
||||
end
|
||||
else
|
||||
stderr:write("grep: ",file,": file not found\n")
|
||||
return false, 2
|
||||
end
|
||||
end
|
||||
end
|
||||
meta.line = nil
|
||||
if not meta.close and curHand then
|
||||
meta.line_num = meta.line_num + 1
|
||||
meta.line = curHand:read("*l")
|
||||
end
|
||||
if not meta.line then
|
||||
curFile = nil
|
||||
if curHand then
|
||||
curHand:close()
|
||||
end
|
||||
return false, meta
|
||||
else
|
||||
return meta, curFile
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function write(part, color)
|
||||
local prev_color = color and getc()
|
||||
if color then setc(color) end
|
||||
io.write(part)
|
||||
if color then setc(prev_color) end
|
||||
end
|
||||
local flush=(f_only or no_only or print_count) and function(m)
|
||||
if no_only and m.hits == 0 or f_only and m.hits ~= 0 then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write('\n')
|
||||
elseif print_count then
|
||||
if include_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
end
|
||||
write(m.hits)
|
||||
write('\n')
|
||||
end
|
||||
end
|
||||
local ec = nil
|
||||
local any_hit_ec = 1
|
||||
local function test(m,p)
|
||||
local empty_line = true
|
||||
local last_index, slen = 1, #m.line
|
||||
local needs_filename, needs_line_num = include_filename, print_line_num
|
||||
local hit_value = 1
|
||||
while last_index <= slen and not m.close do
|
||||
local i, j = m.line:find(p, last_index, plain)
|
||||
local word_fail, line_fail =
|
||||
match_whole_word and not (i and not (m.line:sub(i-1,i-1)..m.line:sub(j+1,j+1)):find("[%a_]")),
|
||||
match_whole_line and not (i==1 and j==slen)
|
||||
local matched = not ((m_only or last_index==1) and not i)
|
||||
if (hit_value == 1 and word_fail) or line_fail then
|
||||
matched,i,j = false
|
||||
end
|
||||
if invert_match == matched then break end
|
||||
if max_matches == 0 then os.exit(1) end
|
||||
any_hit_ec = 0
|
||||
m.hits, hit_value = m.hits + hit_value, 0
|
||||
if f_only or no_only then
|
||||
m.close = true
|
||||
end
|
||||
if flush or quiet then return end
|
||||
if needs_filename then
|
||||
write(m.label, LABEL_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_filename = nil
|
||||
end
|
||||
if needs_line_num then
|
||||
write(m.line_num, LINE_NUM_COLOR)
|
||||
write(':', COLON_COLOR)
|
||||
needs_line_num = nil
|
||||
end
|
||||
local s=m_only and '' or m.line:sub(last_index,(i or 0)-1)
|
||||
local g=i and m.line:sub(i,j) or ''
|
||||
if i==1 then g=trim_front(g) elseif last_index==1 then s=trim_front(s) end
|
||||
if j==slen then g=trim_back(g) elseif not i then s=trim_back(s) end
|
||||
write(s)
|
||||
write(g, MATCH_COLOR)
|
||||
empty_line = false
|
||||
last_index = (j or slen)+1
|
||||
if m_only or last_index>slen then
|
||||
write("\n")
|
||||
empty_line = true
|
||||
needs_filename, needs_line_num = include_filename, print_line_num
|
||||
elseif p:find("^^") and not plain then p="^$" end
|
||||
end
|
||||
if not empty_line then write("\n") end
|
||||
if max_matches ~= math.huge and max_matches >= m.hits then
|
||||
m.close = true
|
||||
end
|
||||
end
|
||||
|
||||
local uptime = computer.uptime
|
||||
local last_sleep = uptime()
|
||||
for meta,status in readLines() do
|
||||
if uptime() - last_sleep > 1 then
|
||||
os.sleep(0)
|
||||
last_sleep = uptime()
|
||||
end
|
||||
if not meta then
|
||||
if type(status) == 'table' then if flush then
|
||||
flush(status) end -- this was the last object, closing out
|
||||
elseif status then
|
||||
ec = status or ec
|
||||
end
|
||||
else
|
||||
for _,p in ipairs(PATTERNS) do
|
||||
test(meta,p)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return ec or any_hit_ec
|
||||
Reference in New Issue
Block a user