Skip to content

FA 0.11 builtin Lua startup code

Bulat-Ziganshin edited this page Oct 8, 2016 · 6 revisions

This is the Lua startup code as included in FA 0.11. You can print actual version of this code by executing fa --print-config.

--[[ **********************************************************************************************************************************
*** Utility functions *****************************************************************************************************************
*************************************************************************************************************************************]]

kb,mb,gb,tb,pb = 1024, 1024^2, 1024^3, 1024^4, 1024^5

function wildcard2pattern(wildcard)
    return '^' .. wildcard:gsub('%p','%%%0'):gsub('%%%*','.*'):gsub('%%%?','.') .. '$'
end

function start_with(str,...)
    for _, substr in ipairs{...} do
        if str:sub(1,#substr) == substr then
            return str:sub(1+#substr)
        end
    end
end
string.start_with = start_with

function in_list(str,list,delim)
    delim = delim or ','
    return string.find (delim..list..delim, delim..str..delim, 1, true)
end
string.in_list = in_list

function table.map(xs,f)
    local result = {}
    for _,x in ipairs(xs) do
      table.insert (result, f(x))
    end
    return result
end

function split(str,sep)
    local t = {}
    for word in str:gmatch('([^'..sep..']+)') do
        table.insert(t,word)
    end
    return t
end
string.split = split


--[[ **********************************************************************************************************************************
*** Handling compression method strings ***********************************************************************************************
*************************************************************************************************************************************]]
function split_method(method)
    return split(method,'+')
end

function join_method(compressors)
     return table.concat(compressors,'+')
end

function split_compressor(compressor)
    return split(compressor,':')
end

function join_compressor(params)
     return table.concat(params,':')
end

function get_compressor_name(compressor)
    return split_compressor(compressor)[1]
end

for name in string.gmatch('GetCompressionMem GetMinCompressionMem GetDecompressionMem GetMinDecompressionMem GetDictionary GetBlockSize GetAlgoMem GetMaxCompressedSize', '%S+') do
    _G[name] = function(method,param) return CompressionService(method, name, param or 0) end
end
for name in string.gmatch('SetDictionary SetBlockSize SetCompressionMem SetDecompressionMem SetMinDecompressionMem', '%S+') do
    _G[name] = function(method,param) return CompressionModify(method,name,param) end
end

STORING              = "storing"
FAKE_COMPRESSION     = "fake"
CRC_ONLY_COMPRESSION = "crc"


--[[ **********************************************************************************************************************************
*** Handling compression groups *******************************************************************************************************
*************************************************************************************************************************************]]
-- Pass each compression group through filter_f(compression_method, group_name).
-- The first returned value, if not nil, assigned back to the compression method.
-- The second returned value, if not nil, assigned back to the group name.
-- Finally, groups having empty compression method are completely removed.
function MapCompressionGroups (filter_f, ...)
    local cmethods, cgroups = command.compression_methods, command.compression_groups
    cgroups[1] = '$default'

    -- Filter method/group through filter_f, removing groups whose compression method became empty
    local m,g = {},{}
    for i in ipairs(cgroups) do
        local method, group  = filter_f (cmethods[i], cgroups[i], ...)
        if method ~= '' then
            table.insert (m, method or cmethods[i])
            table.insert (g, group  or cgroups[i])
        end
    end

    g[1] = ''
    command.compression_methods, command.compression_groups = m, g
end

-- Remove compression groups satisfying `predicate_f`.
-- Alternatively, `predicate_f` may be just a string with name of group to remove.
function RemoveCompressionGroups (predicate_f, ...)
    if type(predicate_f)=='string' then
        local removed_group = predicate_f
        predicate_f = function(method,group) return group==removed_group end
    end
    local function filter_f (method, group, ...)
        return (predicate_f(method,group,...) and '' or nil)
    end
    MapCompressionGroups (filter_f, ...)
end

-- Add new compression group
function AddCompressionGroup (method, group)
    RemoveCompressionGroups(group)
    local g,m = command.compression_groups, command.compression_methods
    table.insert (g, group)
    table.insert (m, method)
    command.compression_groups, command.compression_methods = g, m
end


-- Map each compressor in the `method` through `filter_f`, removing empty strings from result.
-- Nil returned from `filter_f` keeps the compressor intact.
function map_compressors (method, filter_f, ...)
    local cm = {}
    for old_compressor in string.gmatch(method, '[^+]+') do
        local name = get_compressor_name (old_compressor)
        local new_compressor = filter_f (name, old_compressor, ...)
        if new_compressor ~= '' then
            table.insert(cm, new_compressor or old_compressor)
        end
    end
    return join_method(cm)
end

-- Remove from `method` compressors satisfying `predicate_f`.
-- Alternatively, `predicate_f` may be just a string with name of compressor to remove.
function remove_compressors (method, predicate_f, ...)
    if type(predicate_f)=='string' then
        local removed_name = predicate_f
        predicate_f = function(name) return name==removed_name end
    end
    local function filter_f (name, compressor, ...)
        return (predicate_f(name,compressor,...) and '' or compressor)
    end
    return map_compressors (method, filter_f, ...)
end


--[[ **********************************************************************************************************************************
*** Event handling machinery **********************************************************************************************************
*************************************************************************************************************************************]]
for cmd in string.gmatch('ProgramStart ProgramDone CommandStart CommandDone ArchiveStart ArchiveDone Error Warning Option PostOption FileFilter', '%S+') do
    local tabname = 'on'..cmd..'Handlers'

    local function addEventHandler(handler)
        if type(handler)=='string' then
            handler,errmsg = load('local string_match, start_with, bnot, bor, bxor, band, btest = string.match, start_with, bit32.bnot, bit32.bor, bit32.bxor, bit32.band, bit32.btest; return ('..handler..')')
            if handler   then handler = handler()   else print(errmsg) end
        end
        table.insert (_G[tabname], handler)
    end

    local function runEventHandlers(...)
        for _,handler in ipairs(_G[tabname]) do
            result = handler(...)
            if result then return result end
        end
    end

    _G[tabname] = _G[tabname] or {}
    _G['on'..cmd] = addEventHandler
    _G[cmd] = runEventHandlers
end

function PostOption(...)
    for _,handler in ipairs(onPostOptionHandlers) do
        result = handler(...)
        if result then return result end
    end
    if #ExprFileFilters > 0 then
        onFileFilter ('function (name, type, size, time, attr); return ('..table.concat(ExprFileFilters,') and (')..'); end')
    end
end
function AddFileFilter(filter)
    table.insert (ExprFileFilters, filter)
end
ExprFileFilters = ExprFileFilters or {}

function FileFilter(...)
    for _,handler in ipairs(onFileFilterHandlers) do
        result = handler(...)
        if not result then return result end
    end
    return true
end

function noFileFilters()
    return #onFileFilterHandlers==0
end


--[[ **********************************************************************************************************************************
*** Option parsing machinery **********************************************************************************************************
*************************************************************************************************************************************]]
optvalue = optvalue or {}

function parse_num(str)
    str = str:gsub("[',]", '')
    return tonumber(str)
end

function parse_mem(str,spec)
    spec = spec or 'b'
    if str:sub(-1) == 'b' then
        str = str:sub(1,-2)
        spec = 'b'
    end
    local last_char = str:sub(-1)
    if string.find(MEM_SUFFIXES,last_char,1,true) then
        str = str:sub(1,-2)
        spec = last_char
    end
    local n = parse_num(str)
    if n then
        if spec=='^' then
            n = 2^n
        else
            local pos = string.find(MEM_SUFFIXES,spec,1,true)
            if pos  then n = n*(1024^pos) end
        end
    end
    return n
end
MEM_SUFFIXES = 'kmgtpezy^'

function try_parse_num(name,str)
    local n = parse_num(str)
    if n then
        optvalue[name] = n
    else
        return 'cannot parse "'..str..'" as number'
    end
end

function try_parse_mem(name,str,spec)
    local size = parse_mem(str,spec)
    if size then
        optvalue[name] = size
    else
        return 'cannot parse "'..str..'" as size'
    end
end

function try_parse_bool(name,str)
    if str=='' then
        optvalue[name] = true
    else
        return 'extra text "'..str..'" in the option that doesn\'t take any parameters'
    end
end

OPTION_BYTES     = 'b'
OPTION_KILOBYTES = 'k'
OPTION_MEGABYTES = 'm'
OPTION_GIGABYTES = 'g'
OPTION_TERABYTES = 't'
OPTION_PETABYTES = 'p'
OPTION_POWER     = '^'
OPTION_NUMBER    = '%'
OPTION_STRING    = '$'
OPTION_LIST      = '*'
OPTION_BOOL      = '?'

function AddOption(...)
    local option = {...}
    if type(option[2]) ~= "number" then
        table.insert(option,2,0)
    end
    local name, priority, texts, help, parser, post_parser = table.unpack(option)
    if type(parser) == "string" then
        local spec = parser
        if string.find (MEM_SUFFIXES..'b', spec, 1, true)  then  parser = function(param);  return try_parse_mem(name,param,spec);  end
        elseif spec==OPTION_NUMBER                         then  parser = function(param);  return try_parse_num(name,param);       end
        elseif spec==OPTION_STRING                         then  parser = function(param);  optvalue[name] = param;                 end
        elseif spec==OPTION_LIST                           then  parser = function(param);  optvalue[name] = optvalue[name] or {};  table.insert(optvalue[name], param);  end
        elseif spec==OPTION_BOOL                           then  parser = function(param);  return try_parse_bool(name,param);      end
        else print ('Invalid option type: "'..spec..'"')
        end
        if post_parser then
            local orig_post_parser = post_parser
            post_parser = function();  if optvalue[name]  then return orig_post_parser(optvalue[name]) end end
        end
    end
    ModifyOption(name, priority, texts, help, parser, post_parser)
end

function AddOptions(options)
    for _,option in ipairs(options) do
        AddOption(table.unpack(option))
    end
end

function RemoveOption(name)
    ModifyOption(name, 0, '', '')
end