Lapp Framework

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 中的一个小建议 - 类似 'local typespec' 这样的东西,在 lapp.lua 的 178 行之前。这类更改可能会让 'strict' 模式更愉快。

许可

Lapp 可在 Lua 的相同许可证下使用。

版权所有,SteveDonovan, 2009

另请参阅


RecentChanges · preferences
编辑 · 历史
最后编辑于 2015 年 5 月 16 日下午 3:12 GMT (差异)