创建交互式Ruby控制台应用程序

15

我想创建一个交互式应用程序,用户启动后可以通过输入命令(类似于shell)来执行各种任务。

例子:

./myapp.rb  
App says Hi  
Commands:   
  help - display help about command
  open - open task
  do - do action
Start>help open
  open <TaskName>
  opens specified task
Start>open Something  
Something>do SomeAction
  Success!
Something> (blinking cursor here)

我搜索过但没找到任何可以用于控制台交互的Ruby宝石(gem),所以我打算自己写...

我看了一下Thor,但那不完全是我想要的,也许我可以使用它,但不确定...

可能会长这样:

class Tasks
  attr_reader :opened_task

  desc "open <TaskName>", "opens specified task"
  def open(params)
  end

  desc "do <ActionName>", "do specified action"
  def do(params)
  end
end

tasks = Tasks.new
# theoretical Console class
console = Console.new
console.addCommand("open",tasks.method(:open),"open task")
console.addCommand("do",tasks.method(:do),"do action")
console.start("%s>",[*tasks.opened_task])

我的问题是,有哪些 gem 可以用来制作类似的控制台界面?也许已经有人做过类似的东西了吗?我计划使用 HighLine 来进行输入/输出,但你有其他建议可以用什么吗?


应该选择 Ruby 还是自己的语法? - Reactormonk
你所说的Ruby或自己的语法是什么意思?如果你是指使用IRB,那么这不是一个选项... - davispuh
1
为什么不呢?它可以免费提供图灵完备性。 - Reactormonk
1
因为它执行 Ruby 代码,但在我的情况下,只允许使用特定的“命令”,输入不正确的语法不应该抛出异常,也不应该有重新定义或覆盖这些命令的方法... - davispuh
这是一个安全问题吗?而且你可以钩入pry来查找语法错误。重新定义不应该是问题,因为你需要知道它的工作原理。在当前触摸感应时代,由于您希望用户使用命令行而不是 GUI,因此我会相信他们拥有一些权力。但这是您的选择,如果您不想在对象上调用 pry,可以尝试重现很多东西。并且你还可以免费获得API。 - Reactormonk
好的,你可以在irb中展示你如何完成它 ;) - davispuh
6个回答

20
你需要的是一个REPL - 读取 → 计算 → 输出循环
例如,IRB为Ruby语言实现了一个REPL。
这是一个非常简单的应用程序REPL实现:
loop do
  Application::Console.prompt.display
  input = gets.chomp
  command, *params = input.split /\s/

  case command
  when /\Ahelp\z/i
    puts Application::Console.help_text
  when /\Aopen\z/i
    Application::Task.open params.first
  when /\Ado\z/i
    Application::Action.perform *params
  else puts 'Invalid command'
  end
end

\A\z分别匹配字符串的开头和结尾。


好的,谢谢你展示了一种我可以实现控制台类方法“start”的方式 :) - davispuh
@davispuh 我忘记了提示符 - 更新了答案并改进了实现。你的 start 方法应该只包含循环。 - Matheus Moreira
2
我接受了这个答案,因为它建议通过展示部分实现来自己制作CLI,特别是为了满足我所有的需求... - davispuh
Console 是 Ruby 自带的类吗?如果是,我需要引入什么来获取它? - Liron Yahdav
@LironYahdav 不,那个类是我为了回答这个问题而编写的API的一部分。想象一下Console包含了与终端仿真器交互相关的应用程序代码,而prompt是一个读取器方法,它返回REPL配置的提示符。 - Matheus Moreira

5
您也可以尝试使用ripl。(引自文档): 创建并启动一个自定义 shell 就像这样简单:
require 'ripl'
# Define plugins, load files, etc...
Ripl.start

在该项目网站上,有ripl的插件全面列表以及使用ripl的控制台应用程序列表。

这看起来更有前途,但仍需要进行一些配置,而且它还有与pry/irb相同的“功能”,这些我不需要,同时我认为还需要进行相当多的更改/配置... - davispuh
2
说实话,我到目前为止还没有使用过 ripl - 但是从头开始编写自己的解决方案似乎不是最好的选择。只考虑使用箭头键进行移动 - 没有 readline 支持,用户会发誓的。 - Aleksander Pohl
在Windows中,命令历史记录可以使用(上/下箭头),我只需要用Tab键实现自动完成,但是一开始我甚至可以不用它... - davispuh
@davispuh,这不仅仅是关于历史和自动完成。Readline 在 shell 中提供了类似于 Emacs/vi 的绑定,许多用户非常习惯并且认为这是理所当然的,因为它们在 Bash/Zsh 中默认可用。 - lorefnon

5

TTY是一个非常好的宝石工具,可以轻松地完成这种操作。您有很多可以单独使用或与完整工具包一起使用的工具。您可以使用颜色、提示符、执行shell本机命令、与屏幕交互、打印表格、进度条和许多其他有用的命令行元素,使用goop api即可完成。

特别是tty-prompt在询问用户输入方面非常有用。

以下是您所提出情况的简要示例:

require 'tty-prompt'
require 'pastel'

prompt = TTY::Prompt.new
loop do
  cmd, parms* = prompt.ask('user@machine$ ').split /\s/
  case cmd
    when "hola"
      puts "Hola amigo " parms
    when "exit"
      break if prompt.yes?('Do you really want to exit?')
  end
end

谢谢您的评论。我正要添加示例。 - David Goitia
这不完全是我想要的,但这个库可以用来实现你展示的控制台。顺便说一下,当我问这个问题时,这个库还不存在 :D 我使用 https://github.com/JEG2/highline 实现了我的CLI-控制台,它做了非常相似的事情。 - davispuh

5

好的,我为Ruby写了一个创建控制台应用程序的库。实际上,这是一段时间以前的事情,但我刚刚决定发布它。如果与HighLine和Readline一起使用,则支持自动完成。

当我编写它时,没有任何文档或测试/规范,但现在我做了一些。虽然还不多,但对于入门应该足够了。

因此,Gem cli-console 和代码均在GitHub上,这里有用法示例


4

看一下 cliqr ruby gem。它似乎完全符合你的需求。这是 Github 链接,其中有描述性的自述文件:https://github.com/anshulverma/cliqr

它可以直接执行命令或在内置 shell 中执行。

以下是它 Git 仓库中的一个测试用例:

    it 'can execute a sub action from shell' do
      cli = Cliqr.interface do
        name 'my-command'
        handler do
          puts 'base command executed'
        end

        action :foo do
          handler do
            puts 'foo executed'
          end

          action :bar do
            handler do
              puts 'bar executed'
            end
          end
        end
      end

      with_input(['', 'my-command', 'foo', 'foo bar', 'foo bar help']) do
        result = cli.execute %w(my-command shell), output: :buffer
        expect(result[:stdout]).to eq <<-EOS
Starting shell for command "my-command"
my-command > .
base command executed
my-command > my-command.
base command executed
my-command > foo.
foo executed
my-command > foo bar.
bar executed
my-command > foo bar help.
my-command foo bar

USAGE:
    my-command foo bar [actions] [options] [arguments]

Available options:

    --help, -h  :  Get helpful information for action "my-command foo bar" along with its usage information.

Available actions:
[ Type "my-command foo bar help [action-name]" to get more information about that action ]

    help -- The help action for command "my-command foo bar" which provides details and usage information on how to use the command.
my-command > exit.
shell exited with code 0
        EOS
      end
    end

2
class MyAPI
  def self.__is__(text)
    @__is__ = text
  end

  def self.method_added(method)
    @__help__ ||= {}
    @__help__[method.to_s] = @__is__
    @__is__ = nil
  end

  def self.help(of)
    @__help__[of]
  end

  __is__ "open file <file>"
  def open(file)
    #...
  end

  __is__ "do X"
  def do(*params)
    #...
  end

  __is__ "calls help, use help <command>"
  def help(*args, &block)
    self.class.help(*args, &block)
  end
end

MyAPI.new(...).pry

或者你可以使用pry命令,但这会削弱图灵完备性。帮助可能是通过命令实现的,因为我不确定我的方法是否有效。这些方法需要进行防御性编码。我记不得如何使用类变量 :-/


需要进行很多更改/配置才能使其按照我的需求工作...而且我将使用它来完成它并不是为之设计的任务,它还有一些我甚至不需要的“功能”... - davispuh
@davispuh,你试过使用cliqr了吗?它不需要太多的配置,基本上开箱即用。 - nuaavee
@nuaavee 当我写这个问题的时候,它还不存在,但是看起来非常类似于我当时所需要的,只不过比它被创建的时间早了3年 :D - davispuh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接