lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]



I have brought up this before, but now I have some sample code to play with.

The issue is (uniform) command line handling in Lua scripts, so users would be able to more or less trust Lua scripts to work with similar logic as to their command parameters, and programmers would be able to get some help on parsing that 'arg' table their main chunk received.

Less words, more code.  Comments are welcome!

-asko :)

....clipclip....


---=== COMMAND LINE ===---

--
-- [tbl][,err_str]= Loc_RunArgs( arg_tbl, params_tbl )
--
-- Handle flags in the 'arg' table (1..N), calling functions in 'params'
-- (keyed by their flag names).
--
-- Callback functions are given the name of the flag causing their call, and -- any non-flag parameters following them (potential parameters for the flag). -- They return the number of additional parameters used (nil=0,1..). In case of
-- error, the function shall return 'nil', and an error message.
--
-- In other words:
--      [int][,err_str]= function( flag_str [, ...] )
--
-- The whole function returns the table of non-flag parameters (s.a. file names),
-- 1..N indexed, or 'nil' and error string on command line error.
--
local function Loc_RunArgs( arg, params )
    --
    local rest= {}  -- normal, non-flag parameters

    -- [...]= Loc_NonflagParams( arg_tbl, start_int )
    --
    -- Return params from 'arg[start]' onwards, until first flag.
    --
    local function Loc_NonflagParams( arg, start, ... )
        --
        local v= arg[start]
        if not v or string.sub(v,1,1)=="-" then
            return unpack(arg)  -- end of recursion
        else
return Loc_NonflagParams( arg, start+1, unpack(arg), v ) -- tail recursion :)
        end
    end

    local i=1
    while arg[i] do
        local v= arg[i]
        local flag= skip2( string.find( v, "%-+(.+)" ) )

        if not flag then
            table.insert( rest, v )
        else
            local vv= params[flag]
            if not vv then
                return nil, "Unexpected flag: '"..v.."'"
                --
            elseif type(vv)=="string" then
-- Note: we also change the 'flag' var for aliases, so the handler -- will get the same name, no matter which string was leading
                --       to it. This should help differentiation.
                flag= vv
                vv= params[vv]  -- redirect: str->str->func
            end

-- errors in 'params' table come from programmer, not user, so we may -- expect a bit more decency from it. (and error, instead of returning)
            --
            local func= vv
            if type(func)~="function" then
                error( "Not a proper handler for: '"..v.."'" )
            end

-- Now, find out how many non-flag parameters we can provide the call
            --
            local n,err= func( flag, Loc_NonflagParams( arg, i+1 ) )

            if err then
                return nil,err
            end

            if n then
ASSUME( tonumber(n), "Handler returned bad (non- numeric) value: '"..flag.."'" )
                i= i+n  -- skip n
            end
        end
        i= i+1
    end

    return rest
end

-- [int][,err_str]= function( flag_str [, ...] )
--
-- Returns the number of extra arguments it used (1..N, nil for none)

local recursive
local check_links
local check_xml
local strip_comments
local exclude
local upload_ftp
local upload_cache

local params= {
    ["r"]=  function() recursive= true end,
    ["cl"]= function() check_links= true end,
    ["cx"]= function() check_xml= true end,
    ["sc"]= function() strip_comments= true end,
    ["x"]=  function(_,text)
                if not text then
                    return nil,"no exclusion text"  -- syntax error
                end
                exclude= text
                return 1    -- one parameter used
            end,
    ["uf"]= function(_,path)
                if not text then
                    return nil,"no ftp upload path"
                end
                upload_ftp= path
                return 1
            end,
    ["uc"]= function(_,path)
                if not text then
                    return nil,"no upload cache name"
                end
                upload_cache= path
                return 1
            end,
    ["v"]=  function()
                io.stderr:write( "Lumikki v."..VERSION.."\n" )
            end,
    ["l"]=  function(_,fn)
                if not fn then
                    return nil,"no script name"
                end
                if not string.find( fn, "%.[lL][uU][aA]$" ) then
                    fn= fn..".lua"
                end

                if not FileExists(fn) then
                    return nil,"no such script: '"..fn.."'"
                end

                -- We could (should?) build up a sandbox here...
                --
                dofile( fn )
            end,
    ["h"]=  function()
                io.stderr:write( [[
Lumikki is a static website generation and/or XML preprocessing tool.
Copyright (c) 2005-06 Asko Kauppi.

  lumikki [-r | --recursive] [-cl | --check-links] [-cx | --check-xml]
[-sc | --strip-comments] [-x | --exclude text] [-l | -- load myscript[.lua] ] [index.lxml]

  lumikki --recursive  ..other flags..  [-uf | --upload-ftp
user[:passwd]@ftp-host[:port]/path[/] ] [-uc | --upload- cache filename]

  lumikki [-v | --version]
  lumikki [-h | --help]
]] )
            end,
}

-- Long name aliases
--
params["recursive"]=    assert( params["r"] )
params["check-links"]=  assert( params["cl"] )
params["check-xml"]=    assert( params["cx"] )
params["strip-comments"]= assert( params["sc"] )
params["exclude"]=      assert( params["x"] )
params["upload-ftp"]=   assert( params["uf"] )
params["upload-cache"]= assert( params["uc"] )
params["version"]=      assert( params["v"] )
params["load"]=         assert( params["l"] )
params["help"]=         assert( params["h"] )

--
local rest,err= Loc_RunArgs( arg, params )
ASSUME( rest,err )

-- Unimplemented features:
--
if recursive or check_links or check_xml or strip_comments or exclude or
    upload_ftp or upload_cache then
    error "SOME FEATURE NOT IMPLEMENTED YET.  Sorry! :o"
end

-- 'rest' is the filenames that were left after processing params.
local xmls_handled= 0

for i,v in ipairs(rest) do

    if string.find( v, "%.[lL][uU][aA]$" ) then
        --
        if xmls_handled>0 then
error "Give LUA files prior to LXML; so the filters get loaded."
        end

-- Use 'params["l"]' function here, to get uniformity with "--load" flag
        --
        params["l"]( "", v )
    else
        xmls_handled= xmls_handled+1
        if xmls_handled>1 then
error "Multiple source files not supported for now (if they were, LXML->HTML renaming would need to be done, is that okay?)"
        end

        local str= Loc_Filter( rest[i] )
        print(str)
    end
end

if xmls_handled==0 then
    params["h"]( "" )   -- Help :)
end