Lapp 框架

lua-users home
wiki

简化命令行脚本

原始代码位于 http://mysite.mweb.co.za/residents/sdonovan/lapp.zip;现在位于 PenlightLibraries,请参阅 [1] 中的说明。

Lapp 是一个小型且专注的 Lua 模块,旨在简化标准命令行解析并使其更直观。它实现了标准的 GNU 风格,即短选项以一个字母开头,并以 '-' 开头,可能还有一个以 '--' 开头的长选项。通常,需要参数的选项期望在下一个参数中找到它(例如 'gcc test.c -o test'),但单个需要数字参数的短选项可以省略空格(例如 'head -n4 test.c')。

Lapp 将尽可能地将参数转换为等效的 Lua 类型,即转换数字并将文件名转换为文件对象。如果任何转换失败,或者缺少必需的参数,将发出错误并输出使用说明文本。因此,有两个必要的任务,提供标志和选项名称,并将它们与类型关联。

对于任何非平凡的脚本,即使是个人使用,也需要提供使用说明文本。Lapp 的新颖之处在于它从这一点开始,并定义了一种松散的格式用于使用字符串,该字符串可以指定参数的名称和类型。

一个例子将使这一点更清楚

-- scale.lua
  require 'lapp'
  local args = lapp [[
  Does some calculations
    -o,--offset (default 0.0)  Offset to add to scaled number
    -s,--scale  (number)  Scaling factor
     <number> (number)  Number to be scaled
  ]]

  print(args.offset + args.scale * args.number)

这是一个使用此脚本的命令行会话

  D:\dev\lua\lapp>lua scale.lua
  scale.lua:missing required parameter: scale

  Does some calculations
   -o,--offset (default 0.0)  Offset to add to scaled number
   -s,--scale  (number)  Scaling factor
    <number> (number )  Number to be scaled

  D:\dev\lua\lapp>lua scale.lua -s 2.2 10
  22

  D:\dev\lua\lapp>lua scale.lua -s 2.2 x10
  scale.lua:unable to convert to number: x10

  ....(usage as before)

Lapp 使用字符串中有两种类型的行是有意义的;选项行和参数行。选项行给出短选项,可选地后跟相应的长选项。括号中的类型说明符可能在后面。类似地,参数行以 '<' PARAMETER '>' 开头,后跟类型说明符。类型说明符的形式为 '(default ' VALUE ')' 或 '(' TYPE ')'; 默认说明符意味着参数或选项具有默认值,并且是非必需的。TYPE 是 'string'、'number'、'file-in' 或 'file-out' 之一;VALUE 是一个数字,是 ('stdin'、'stdout'、'stderr') 之一或是一个标记。该行的其余部分不会被解析,可以用于解释性文本。

此脚本显示了指定的参数名称与输出表中的字段之间的关系。

  -- simple.lua
  local args = require ('lapp') [[
  Various flags and option types
    -p          A simple optional flag, defaults to false
    -q,--quiet  A simple flag with long name
    -o  (string)  A required option with argument
    <input> (default stdin)  Optional input file parameter
  ]]

  for k,v in pairs(args) do
      print(k,v)
  end

我刚刚将args表中的所有值都转储出来了;请注意,args.quiet已变为true,因为它已指定;args.p默认为false。如果选项有长名称,则该名称将优先用作字段名称。对于简单的标志,类型或默认值说明符不是必需的,因为默认类型为boolean

参数input已设置为一个打开的只读文件对象 - 我们知道它必须是一个只读文件,因为这是默认值的类型。字段input_name是自动生成的,因为它通常有助于访问原始文件名。

  D:\dev\lua\lapp>simple -o test -q simple.lua
  p       false
  input   file (781C1BD8)
  quiet   true
  o       test
  input_name      simple.lua
  D:\dev\lua\lapp>simple -o test simple.lua one two three
  1       one
  2       two
  3       three
  p       false
  quiet   false

  input   file (781C1BD8)
  o       test
  input_name      simple.lua

请注意,任何提供的额外参数都将以整数索引放入结果表中,即args[i],其中i从 1 到#args

对于具有快速明确定义的任务的简短脚本,文件实际上不必显式关闭,因为垃圾回收文件对象的最终结果是关闭它们。

强制参数范围

类型说明符也可以是'(' MIN '..' MAX ')'的形式。

  require 'lapp'
  local args = lapp [[
  Setting ranges
    <x> (1..10)  A number from 1 to 10
    <y> (-5..1e6) Bigger range
  ]]

  print(args.x,args.y)

这里的意思是该值大于或等于 MIN 且小于或等于 MAX;没有规定强制参数为整数。

您还可以定义可以在类型说明符中使用的自定义类型

    require ('lapp')

    lapp.add_type('integer','number',
        function(x)
            lapp.assert(math.ceil(x) == x, 'not an integer!')
        end
    )

    local args =  lapp [[
        <ival> (integer) Process PID
    ]]

    print(args.ival)

lapp.add_type 接受三个参数,一个类型名称、一个转换器和一个约束函数。约束函数应在某些条件不满足时抛出断言;我们使用lapp.assert,因为它以命令行脚本的标准方式失败。转换器参数可以是 Lapp 已知的类型名称,也可以是接受字符串并生成值的函数。

'varargs' 参数数组

    require 'lapp'
    local args = lapp [[
    Summing numbers
        <numbers...> (number) A list of numbers to be summed
    ]]

    local sum = 0
    for i,x in ipairs(args.numbers) do
        sum = sum + x
    end
    print ('sum is '..sum)

参数number后面有一个'...',表示此参数是一个'varargs'参数。它必须是最后一个参数,并且args.number将是一个数组。

考虑来自类 Unix 操作系统的head实用程序的此实现

    -- implements a BSD-style head
    -- (see http://www.manpagez.com/man/1/head/osx-10.3.php)

    require ('lapp')

    local args = lapp [[
    Print the first few lines of specified files
       -n         (default 10)    Number of lines to print
       <files...> (default stdin) Files to print
    ]]

    -- by default, lapp converts file arguments to an actual Lua file object.
    -- But the actual filename is always available as <file>_name.
    -- In this case, 'files' is a varargs array, so that 'files_name' is
    -- also an array.
    local nline = args.n
    local nfile = #args.files
    for i = 1,nfile do
        local file = args.files[i]
        if nfile > 1 then
            print('==> '..args.files_name[i]..' <==')
        end
        local n = 0
        for line in file:lines() do
            print(line)
            n = n + 1
            if n == nline then break end
        end
    end

请注意,我们如何访问所有文件名,因为自动生成的字段files_name也是一个数组!

(这可能不是一个非常周到的脚本,因为 Lapp 将打开提供的所有文件,并且只在脚本结束时关闭它们。有关另一个实现,请参见xhead.lua示例。)

标志和选项也可以声明为 vararg 数组,并且可以出现在任何地方。请记住,短选项可以组合(如 'tar -xzf'),因此拥有 '-vvv' 是完全合法的。但通常args.v的值只是一个简单的true

    local args = require ('lapp') [[
       -v...  Verbosity level; can be -v, -vv or -vvv
    ]]
    vlevel = not args.v[1] and 0 or #args.v
    print(vlevel)

vlevel赋值有点 Lua 巫术,所以考虑以下情况

定义参数回调

如果脚本实现了 lapp.callback,那么 Lapp 会在解析完每个参数后调用它。回调函数会传入参数名称、未解析的原始值和结果表。它在值被赋值后立即被调用,因此相应的字段可用。

    require ('lapp')

    function lapp.callback(parm,arg,args)
        print('+',parm,arg)
    end

    local args = lapp [[
    Testing parameter handling
        -p               Plain flag (defaults to false)
        -q,--quiet       Plain flag with GNU-style optional long name
        -o  (string)     Required string option
        -n  (number)     Required number option
        -s (default 1.0) Option that takes a number, but will default
        <start> (number) Required number argument
        <input> (default stdin)  A parameter which is an input file
        <output> (default stdout) One that is an output file
    ]]
    print 'args'
    for k,v in pairs(args) do
        print(k,v)
    end

这会产生以下输出

    D:\dev\lua\lapp>args -o name -n 2 10 args.lua
    +       o       name
    +       n       2
    +       start   10
    +       input   args.lua
    args
    p       false
    s       1
    input_name      args.lua
    quiet   false
    output  file (781C1B98)
    start   10
    input   file (781C1BD8)
    o       name
    n       2

为什么回调函数有用?在某些情况下,您可能希望在解析参数后立即采取行动。

反馈

非常有趣的想法!也许这会导致其他编程语言的克隆 :) 无论如何,感谢您提出这种方法并编写代码。

在 lapp.lua 中有一个小建议 - 在 lapp.lua 的第 178 行之前添加类似 'local typespec' 的内容。这种更改可能会让 'strict' 更开心。

许可

Lapp 在与 Lua 相同的许可下提供。

版权所有,SteveDonovan,2009

另请参阅


最近更改 · 偏好设置
编辑 · 历史记录
最后编辑于 2015 年 5 月 16 日下午 9:12 GMT (差异)