Wait the light to fall

在 Raku 中制作命令行工具

焉知非鱼

准备 #

zef install App::Mi6  # 安装模块骨架工具
mi6 new FakeStreaming # 创建一个模块骨架

使用 tree 看一下目录结构:

├── Changes
├── LICENSE
├── META6.json
├── README.md
├── bin
│   └── fake-stream
├── dist.ini
├── lib
│   └── FakeStreaming.pm6
└── t
    └── 01-basic.t

我们主要看 META6.json, bin 目录, lib 目录。

  • META6.json
{
  "authors" : [
    ""
  ],
  "build-depends" : [ ],
  "depends" : ["PKafka"],
  "description" : "a commandline tool to simulate socket streraming mock and kafka producer",
  "license" : "Artistic-2.0",
  "name" : "FakeStreaming",
  "perl" : "6.c",
  "provides" : {
    "FakeStreaming" : "lib/FakeStreaming.pm6"
  },
  "resources" : [ ],
  "source-url" : "",
  "tags" : [ ],
  "test-depends" : [ ],
  "version" : "0.0.1"
}

这个文件是 zef 安装工具所需的, depends 一栏填写该模块所需要的依赖, name 为模块名, provides 为模块的 lib 路径。其余可选填。

创建模块 #

  • lib

存放 .pm6 模块的地方。这个模块我定义了俩个方法, 一个是模拟 socket 实时流, 一个是模拟 Kafka producer(每隔 1s 发一条数据到 Kafka, 然后等待 sleeping 秒, 然后继续这个过程)。功能很简单。

use v6.c;
use PKafka::Producer;

unit class FakeStreaming:ver<0.0.1>;

class Data {
    has $.host is rw       = '0.0.0.0';
    has $.port is rw       = 3333;
    has $.vin is rw        = 'LSJA0000000000091';
    has $.last_meter is rw = 0;
    has $.sleeping is rw   = 5;
    has $.rate is rw       = 1;
    has Str $.brokers is rw    = "127.0.0.1";
    has Str $.topics is rw     = "xs6-nation-test";
    has Int $.partitions is rw = 6;

    method feed() {
        react {
            whenever IO::Socket::Async.listen($!host, $!port) -> $conn {
                react {
                    my Bool:D $ignore = True;

                    whenever Supply.interval($!sleeping).rotor(1, 1 => 1) {
                        $ignore = !$ignore;
                    }

                    whenever Supply.interval($!rate) {
                        next if $ignore;
                        print sprintf("\{'vin':'%s','createTime':%s,'mileage':%s}\n", $!vin, DateTime.now.posix, $!last_meter);
                        $conn.print: sprintf("\{'vin':'%s','createTime':%s,'mileage':%s}\n", $!vin, DateTime.now.posix, $!last_meter++);
                    }

                    whenever signal(SIGINT) {
                        say "Done.";
                        done;
                    }
                }
            }

            CATCH {
                default {
                    say .^name, ': ', .Str;
                    say "handled in $?LINE";
                }
            }
        }
    }

    method producer() {            
                react {
                    my $producer = PKafka::Producer.new(topic => $!topics, brokers => $!brokers);
                    my Bool:D $ignore = True;
                    whenever Supply.interval($!sleeping).rotor(1, 1 => 1) {
                        $ignore = !$ignore;
                    }

                    whenever Supply.interval($!rate) {
                        next if $ignore;
                        print sprintf("\{'vin':'%s','createTime':%s,'mileage':%s}\n", $!vin, DateTime.now.posix, $!last_meter);
                        my Int $partition = (0..^$!partitions).pick;
                        my Str $payload = sprintf("\{'vin':'%s','createTime':%s,'mileage':%s}", $!vin, DateTime.now.posix, $!last_meter++);
                        my Str $key = $!vin;
                        $producer.put(partition => $partition, payload => $payload, key => $key);
                    }

                    whenever signal(SIGINT) {
                        say "Done.";
                        done;
                    }
                }
    }

}

=begin pod

=head1 NAME

FakeStreaming - blah blah blah

=head1 SYNOPSIS

=begin code :lang<raku>

use FakeStreaming;

=end code

=head1 DESCRIPTION

FakeStreaming is ...

=head1 AUTHOR

 <>

=head1 COPYRIGHT AND LICENSE

Copyright 2019 

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod

创建命令行工具 #

  • bin 存放可执行文件的地方

这里我定义了两个 multi sub MAIN, 运行的时候传不同的参数就可以调用对应的函数了:

#!/usr/bin/env raku

use FakeStreaming;

multi sub MAIN(
    :$host        = '0.0.0.0', 
    :$port        = 3333, 
    :$vin         = 'LSJA0000000000091', 
    :$last_meter  = 0, 
    :$sleeping    = 10, 
    :$rate        = 1
    ) {
    my $app = FakeStreaming::Data.new(
        :$host, 
        :$port, 
        :$vin, 
        :$last_meter, 
        :$sleeping, 
        :$rate
    );
    $app.feed();
}

multi sub MAIN(
    :$vin         = 'LSJA0000000000091', 
    :$last_meter  = 0, 
    :$sleeping    = 10, 
    :$rate        = 1,
    :$brokers     = "127.0.0.1",
    :$topics      = "xs6-nation-test",
    :$partitions  = 6
) {
       my $app = FakeStreaming::Data.new(
        :$vin, 
        :$last_meter, 
        :$sleeping, 
        :$rate,
        :$brokers,
        :$topics,
        :$partitions
    );
    $app.producer();
}

安装命令行工具 #

这样一个命令行工具的准备工作就完成了, 因为这是自己用的, 所以我们安装在本地即可:

rm -rf .precomp # 删除预编译文件
zef install . --force-install # 为方便调试, 强制安装

安装后的可执行文件在 ~/.raku/bin/ 下面:

which fake-stream
~/.raku/bin/fake-stream

它其实还是个文本文件, 我们打开看一下:

#!/usr/bin/env raku
sub MAIN(:$name, :$auth, :$ver, *@, *%) {
    CompUnit::RepositoryRegistry.run-script("fake-stream", :$name, :$auth, :$ver);
}

这段代码可能是仓库注册相关的, 官网没有详细解释, 只给出了源代码地址

调用命令行 #

运行很简单拉,使用命名参数即可:

fake-stream  --vin='LSJA0000000000091'  --last_meter=0 --sleeping=5 --rate=1 --brokers='127.0.0.1' --topics='xs6-nation-test' --partitions=6

这会调用 producer, 给 kafka 发数据。

总结 #

简单来说, 就是三步:

  • 1)创建 Raku 模块(.pm6)放在 lib
  • 2)创建命令行程序(不带后缀.pl6)放在 bin
  • 3)使用 zef install . 安装命令行程序

如果你在第二步带了后缀, 例如 .pl6 那么你在执行的时候, 就需要加上后缀了, 这很不 Rakuish! App::Task 就是忘记去掉 .pl6 了。哈哈哈哈, 我自己在本地安装不带后缀的, 很好用!