带退出码的增强
— 焉知非鱼categories: [“Raku”] #
在我的上一篇文章中,我找到了一个很好的方法来匹配 exitcode。我想把它扩展到与 STDERR 匹配,如果 exitcode 是非零。我已经有了一种方法来捕获一个管道的所有错误流。
px<find /tmp» |» px<your-script-here> |» @a :stderr(Capture);
我使用 Capture
(类型对象)的方式和我们使用 *
或 Whatever 一样。它只是表示,神奇的东西应该发生。这个神奇的东西归结为把所有 STDERR
流粘到一个二维数组中。如果我想处理错误,我可能想对 exitcode、shell 命令的名称和它输出到 STDERR
的部分进行匹配。像下面这样的语法会很好。
my $ex = Exitcode.new: :STDERR(<abc def ghi>), :exitint(42), :command<find>;
given $ex {
when ‚find‘ & 42 & /def\s(\S+)/ {
note „find terminated with 42 and $0“;
}
}
事实证明,在 Junction 中得到对 Str
、Numeric
和 Regex
的匹配是很容易做到的。我们需要做的就是增强 Regex
。
augment class Regex {
multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
?$ex.STDERR.join(„\n“).match(self)
}
}
augment class Int {
multi method ACCEPTS(Int:D: Shell::Piping::Exitcode:D $ex) {
self.ACCEPTS($ex.exitint)
}
}
augment class Str {
multi method ACCEPTS(Str:D: Shell::Piping::Exitcode:D $ex) {
self.ACCEPTS($ex.command)
}
}
这只对匹配有效。我无法用这种方式将 (\S+)
捕获到 $0
。我们知道并喜欢 Str.match
能做到这一点-看起来很容易。让我们从它那里偷学代码吧!
proto method match(|) { $/ := nqp::getlexcaller('$/'); {*} }
所以 .match
要做的第一件事就是将本地的 $/
与调用者的 $/
绑定。因此,任何对本地版本的改变都会实际改变调用者的版本。我试着模仿了一下,但没有成功。无论是 nqp 方式还是稍微干净的 Raku 方式都不行。
augment class Regex {
multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
CALLER::<$/> := $/;
?$ex.STDERR.join(„\n“).match(self)
}
}
至少在 given/when
块中不行。简单的说 $ex ~~ defs(\S+);
在全局作用域内就能正常工作。我甚至得到了一个错误信息:OUTER
中不存在 \$
。考虑到根据定义,它应该存在于每一个块中,这很奇怪。
我们可以用下面的构造来检查调用者的词法作用域。
say CALLER::.keys;
say Backtrace.new.gist;
这将输出词法和有多少堆栈帧。而事实上,given/when
确实引入了一个额外的栈帧,它包含 $_
但不包含 $/
(以及其他一些零碎的东西)。由于 caller 是一个 Stash,什么是转半的 Hash,我们可以像往常一样使用 :existence
,再增加一个 caller。
augment class Regex {
multi method ACCEPTS(Regex:D: Shell::Piping::Exitcode:D $ex) {
CALLER::<$/>:exists ?? (CALLER::<$/> := $/) !! (CALLER::CALLER::<$/> := $/);
?$ex.STDERR.join(„\n“).match(self)
}
}
现在,它的工作原理与设想的一样。
然而-实际上 HOWEVER-我正在增强一个内置类。这是有风险的。我不是一个人。我们得到了一个 ircbot,可以在生态系统的源代码中 grep。
20:01 < gfldex> greppable6: augment class
20:01 < greppable6> gfldex, 49 lines, 26 modules: https://gist.github.com/4088b5b8e7b51d94276b15500c240a5f
当我们编写模块时,我们会引入自定义名称所在的作用域。一个模块的用户可以决定将这些名字导入到用户控制的作用域中。当我们增强模块时,我们将一个新的名字引入到一个全局的作用域中。如果两个模块有相同的想法会怎样?如果注入的方法实际上是一个多,那么它很可能会工作。但它不一定要这样。当两个或更多的 multi
候选者具有相同的优先权时,第一个被发现的将获胜。通过在一个非 multi
上使用 augment
,我们有机会得到一个错误信息。如果我们通过 MOP
添加一个方法,我们就不会。如果我想在 when
语句或代码块中允许智能匹配,我需要提供在 ~~
的 LHS 上有我的自定义类的能力。所以没有办法绕过 augment
。我甚至可以说,这就是在语言设计中加入 augment
的原因。当我们考虑到 Raku 越来越不年轻,语言版本不断增加时,情况就会变得更糟。一个测试马虎的模块可能会与语言版本增加的新方法发生碰撞。我们是否应该在 compunit
中强制执行一个带有语言版本的使用语句,并带有一个增强语句?
这真的困扰着我。我们可以使用 META6
并添加字段 augments: "Cool,Int,Regex"
。这样 zef 就有机会发现碰撞并提供一个警告。遗憾的是,我们没有办法强制执行这个功能(因为 EVAL
)。我会花更多的时间来思考这个问题,可能会开始一个解决问题的问题。