Creating interactive ruby console application - ruby

I want make interactive application where user launches it and can do various task by typing commands (some kind of shell)
example:
./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)
I searched but couldn't find any ruby gems that I could use specially for console interaction, so I'm about to my make my own...
I looked at Thor, but that's not exactly as I want, maybe I could use it, but not sure...
it could look something like:
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])
so my question is, what gems I could use to make such console class? maybe someone have already made something similar?
I plan using HighLine for input/output, but any other suggestion what could I use?

What you want is a REPL – Read → Evaluate → Print Loop.
IRB, for example, implements a REPL for the Ruby language.
Here's a very simple implementation of your application's 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 and \z match the start of the string and the end of the string, respectively.

You could also try ripl. (from the documentation):
Creating and starting a custom shell is as simple as:
require 'ripl'
# Define plugins, load files, etc...
Ripl.start
There is a comprehensive list of plugins for ripl as well as list of console applications using ripl on the projects website.

ok, so I made this library for creating console applications in ruby. Actually it was some while ago, but only just decided to release it. It does support auto-completion if used with HighLine and Readline.
When I wrote it there wasn't any documentation nor tests/specs, but now I made some. Still not much but for beginning should be ok.
So gem cli-console and
code is at GitHub, here's usage example

TTY is a really good gem for easily doing this sort of things. You have plenty of tools which can work alone or with the full toolKit. You can use colors, prompts, execute shell natives, interact with the screen, print tables, progressbars and many other useful elements of command lines whith the easy of a goop api.
Particularly tty-prompt is really useful for asking for user input.
A brief example for the case you proposed:
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

Take a look at cliqr ruby gem. It looks like exactly what you need. Here is the github link with a descriptive readme: https://github.com/anshulverma/cliqr
It can execute the commands directly or within a inbuilt shell.
Here is a test case from its git repo:
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

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
Or you could use pry commands, but that defeats the
turing-completeness. Help might be implemented using commands, as I'm
not sure how well my approach works out. Those methods need to be
coded defensive. I can't remember how to use class variables :-/

Related

Use a command line option to call a method

So I guess this is kind of related to my last question, but I was wondering if there was a way to call a method by using a command line option. Say you had a method like this:
def b
puts "Hello brian"
end
is there a way to write something like this:
ruby mine.rb -b
and get this
Hello brian
I already tried looking for this online and discovered OptionParser but I have yet to discover anything involving the OptionParser calling a method previously created.
There are a lot of ways to do this, depending on the use case. The below code is taken from the Ruby docs with the extra method added.
Realistically you'd probably want a class that handles the different options and encapsulates the method instead of having them at the file scope.
#!/usr/bin/env ruby
require 'optparse'
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-b", "Run method 'b'") do |v|
options[:b] = true
end
end.parse!
def b
puts "Hello Brian"
end
if options[:b]
b
end
I've also added a shebang at the top that will automatically call ruby. As long as your script file is executable you can call it directly (ie. ./mine.rb -b).

A ruby script to run other ruby scripts

If I want to run a bunch of ruby scripts (super similar, with maybe a number changed as a commandline argument) and still have them output to stdout, is there a way to do this?
i.e a script to run these:
ruby program1.rb input_1.txt
ruby program1.rb input_2.txt
ruby program1.rb input_3.txt
like
(1..3).each do |i|
ruby program1.rb input_#{i}'
end
in another script, so I can just run that script and see the output in a terminal from all 3 runs?
EDIT:
I'm struggling to implement the second highest voted suggested answer.
I don't have a main function within my program1.rb, whereas the suggested answer has one.
I've tried this, for script.rb:
require "program1.rb"
(1..6).each do |i|
driver("cmd_line_arg_#{i}","cmd_line_arg2")
end
but no luck. Is that right?
You can use load to run the script you need (the difference between load and require is that require will not run the script again if it has already been loaded).
To make each run have different arguments (given that they are read from the ARGV variable), you need to override the ARGV variable:
(1..6).each do |i|
ARGV = ["cmd_line_arg_#{i}","cmd_line_arg2"]
load 'program1.rb'
end
# script_runner.rb
require_relative 'program_1'
module ScriptRunner
class << self
def run
ARGV.each do | file |
SomeClass.new(file).process
end
end
end
end
ScriptRunner.run
.
# programe_1.rb
class SomeClass
attr_reader :file_path
def initialize(file_path)
#file_path = file_path
end
def process
puts File.open(file_path).read
end
end
Doing something like the code shown above would allow you to run:
ruby script_runner.rb input_1.txt input_2.txt input_3.txt
from the command line - useful if your input files change. Or even:
ruby script_runner.rb *.txt
if you want to run it on all text files. Or:
ruby script_runner.rb inputs/*
if you want to run it on all files in a specific dir (in this case called 'inputs').
This assumes whatever is in program_1.rb is not just a block of procedural code and instead provides some object (class) that encapsulates the logic you want to use on each file,meaning you can require program_1.rb once and then use the object it provides as many times as you like, otherwise you'll need to use #load:
# script_runner.rb
module ScriptRunner
class << self
def run
ARGV.each do | file |
load('program_1.rb', file)
end
end
end
end
ScriptRunner.run
.
# program_1.rb
puts File.open(ARGV[0]).read

Call ruby function from command-line

How can I directly call a ruby function from the command-line?
Imagine, I would have this script test.rb:
class TestClass
def self.test_function(some_var)
puts "I got the following variable: #{some_var}"
end
end
If this script is run from the command-line (ruby test.rb), nothing happens (as intended).
Is there something like ruby test.rb TestClass.test_function('someTextString')?
I want to get the following output: I got the following variable: someTextString.
First the name of the class needs to start with a capital letter, and since you really want to use a static method, the function name definition needs to start with self..
class TestClass
def self.test_function(someVar)
puts "I got the following variable: " + someVar
end
end
Then to invoke that from the command line you can do:
ruby -r "./test.rb" -e "TestClass.test_function 'hi'"
If you instead had test_function as an instance method, you'd have:
class TestClass
def test_function(someVar)
puts "I got the following variable: " + someVar
end
end
then you'd invoke it with:
ruby -r "./test.rb" -e "TestClass.new.test_function 'hi'"
Here's another variation, if you find that typing ruby syntax at the command line is awkward and you really just want to pass args to ruby. Here's test.rb:
#!/usr/bin/env ruby
class TestClass
def self.test_function(some_var)
puts "I got the following variable: #{some_var}"
end
end
TestClass.test_function(ARGV[0])
Make test.rb executable and run it like this:
./test.rb "Some Value"
Or run it like this:
ruby test.rb "Some Value"
This works because ruby automatically sets the ARGV array to the arguments passed to the script. You could use ARGV[0] or ARGV.first to get the first argument, or you could combine the args into a single string, separated by spaces, using ARGV.join(' ').
If you're doing lots of command-line stuff, you may eventually have a use for Shellwords, which is in the standard ruby lib.
If you have multiple arguments to call in a example like this:
class TestClass
def self.test_function(some_var1, some_var2)
puts "I got the following variables: #{some_var1}, #{some_var2}"
end
end
run it like this (the arguments need to be comma separated in this case)
ruby -r "./test.rb" -e "TestClass.new.test_function 'hi','Mike'"
#!/usr/bin/env ruby
class A
def run
p :Hello_world
end
self
end.new.run
The usual way to script Ruby is to just use the top level execution environment called main. You can just start defining methods and code you write outside of a class, and these will be executed directly. (BTW, code inside a class but outside any method will run "by itself" also.)
Anyway, I'm with you ... I like writing all code in a named class and instantiating that class, so you can combine the techniques .. have the class return its own object .. and then use just a little of that top level code to allocate, initialize, and then dot into the class.
With this, you can just type $ ruby test.rb and it will do what you want. Or you can chmod +x test.rb; ./test.rb since we did add a shebang.
If you are working on a command line interface, then I would suggest to have a look at thor.
Thor directly maps your commands to methods within the defined class, see the thor wiki for an example.
Just an extension to Ingenu's answer for the case that the function does not print something out, but does return something.
We would have the following test.rb
class TestClass
def self.test_function(some_var)
return "I got the following variable: " + some_var
end
end
Then to invoke that from the command line and get the return value:
ruby -r "./test.rb" -e "puts TestClass.test_function('hi')"
If you know that how to call an rb file from commandline
ruby yourfile.rb
This can do the whole trick for you.
What you have done is, just defined your methods in the class. Now you can call it below the definition. If you still want to read, wide open your eyes
class TestClass
def self.test_function(some_var)
puts "I got the following variable: #{some_var}"
end
test_function(var)
end

Thor Executable - Ignore Task Name

The thor wiki page, Making an Exectable, shows you how to create a thor powered CLI command that looks something like this:
bash
./mythorcommand foo
This requires you to pass in the thor task foo as the first argument.
I can also run a thor executable without any arguments using thor's default_method:
bash
./mythorcommand
However, I'd like to pass in a variable string as the first argument:
bash
./mythorcommand "somevalue"
This doesn't work because thor commands expect the first argument to the be a task name. Is there a way to ignore the task name and send the first argument to a default method?
If this functionality doesn't exist, I think it would be very useful to add a method that would pass all commandline arguments into one task/method:
class MyThorCommand < Thor
only_method :default
def default(*args)
puts args.inpsect
end
end
MyThorCommand.start
You should extend from Thor::Group and that call start method
class Test < Thor::Group
desc "Act description"
def act
puts "do smth"
end
end
Test.start
I found a rather 'strange' solution for this problem that is working quite well with me.
You add a default task to Thor. Than you add the method_missing so that you can trick Thor into passing the default method as an argument if there are parameters to your application.
Taking from your example, the solution would look like this:
class MyThorCommand < Thor
default_task :my_default
desc "my_default", "A simple default"
def my_default(*args)
puts args.inspect
end
def method_missing(method, *args)
args = ["my_default", method.to_s] + args
MyThorCommand.start(args)
end
end
MyThorCommand.start(ARGV)
If this is in the file "my_thor.rb" an execution "ruby my_thor.rb foo bar" would show '["foo", "bar"]' as a result.
Hope it helps.
Though this does not exactly solve your problem, one alternative might be using Thor.map to invoke a command by only giving an option flag:
map '-F' => 'foo'
Now you can also pass parameters
mythorcommand -F bar # => invokes foo("bar")

How to view/save/load work space in ruby's interactive mode

I need an interactive environment to play with some algorithm stuff. I want to be able to view what's been defined (data, function) so far and be able save/load so I can continue from a previous saved snapshot if something went wrong. Since I chose ruby as my main scripting language, I hope it had these features built in.
If ruby interactive mode does not provide these functionality, what else you recommend for that?
Thanks
So here’s a technique that will append commands entered in your IRB session to a file in your home directory (idea from ruby-talk:58931). Put the following in your .irbrc:
module Readline
module History
LOG = "#{ENV['HOME']}/.irb-history"
def self.write_log(line)
File.open(LOG, 'ab') {|f| f << "#{line}
"}
end
def self.start_session_log
write_log("
# session start: #{Time.now}
")
at_exit { write_log("
# session stop: #{Time.now}
") }
end
end
alias :old_readline :readline
def readline(*args)
ln = old_readline(*args)
begin
History.write_log(ln)
rescue
end
ln
end
end
Readline::History.start_session_log
You should check out the sketches gem which let's you prototype code in a temporary file in your preferred editor. I don't think it supports snapshots.
In irb I would use it as follows:
>> sketch
# Write some code in an editor ...
# Lists sketches and their code
>> sketches
# Reopens the first sketch from above
>> sketch 1
If you want a more powerful interactive prototyping environment, see boson.

Resources