Open Editor From Command Line and Fetch Input - ruby

I'm currently working on a feature for CocoaPods, a Ruby gem. There's an existing command that accepts a number of options. I'd like to add an extra option that enables the user to enter a custom message by opening the default text editor and, when the user saves and quits the editor, the message is fed to the command that was executed.
What I want to replicate is how you can add -m to the git commit command to have you enter a commit message. I have little experience with creating command line tools so any help is much appreciated.
The goal is to execute a specific command command --message, open the editor, have the user enter a custom message, and execute the command with the custom message being one of the arguments stored in argv.

The common workflow is:
the caller application creates a temporary file;
determines the default editor (for Debian-based it would be /usr/bin/editor, for other linuces — the content of shell variable $EDITOR, etc);
runs a shell command in a subshell with Kernel#system (not with backticks!);
waits for it to exit;
determines the exit code, and skips following if it is not 0;
reads the content of temporary file, created in step 1 and removes this file.
In ruby that would be like:
▶ f = Tempfile.new 'cocoapods'
#⇒ #<File:/tmp/am/cocoapods20151120-6901-u2lubx>
-rw------- 1 am am 0 nov 20 15:03 /tmp/am/cocoapods20151120-6901-u2lubx
▶ path = f.path
#⇒ "/tmp/am/cocoapods20151120-6901-u2lubx"
▶ f.puts 'This content is already presented in file'
#⇒ nil
▶ f.close # HERE MUST BE ENSURE BLOCK, BUT FOR THE SAKE OF AN EXAMPLE...
#⇒ nil
▶ system "editor #{path}"
#⇒ Vim: Warning: Output is not to a terminal
If you are testing this in console, just type anything, followed by Esc:wq. In real life there will be normal vim (or what the default editor is) opened.
▶ File.read path
#⇒ "GGGGGGGGGThis content is already presented in file\n"
All together:
#!/usr/bin/env ruby
require 'tempfile'
f = Tempfile.new 'cocoapods'
path = f.path
f.puts 'This content is already presented in file'
f.close # HERE MUST BE ENSURE BLOCK, BUT FOR THE SAKE OF AN EXAMPLE...
system "editor #{path}"
puts File.read path

Related

Console keeps waiting for input right after executing without doing anything prior the gets statement in Ruby

I'm new to ruby and this issue is bugging me for a while . Whenever i use gets to take user input , my gets statement is executed right after i run the file .I'm using git Bash to run my file.rb file ,
puts "some unnecessary text"
puts "Hello world"
puts "now you should input something"
x = gets.chomp
puts 36
puts "your input is " + x + " right?"
the program should print the first 3 lines before waiting for an input but it waits for the input right after i run it
$ruby file.rb
|
it waits for eternity unless I press enter . If i write something,
$ ruby file.rb
myInput
some unnecessary text
Hello world
now you should input something
36
your input is myInput right?
it runs okay . So I'm forced to write my input at the beginning .
It's not much of a problem right now but it'll cause a lot if headaches when i write bigger and more complex code . Any solutions ?
ps: It seems the problem only occurs with git Bash (windows) . Powershell works just fine .
It seems that the standard output is buffered.
Try to put at the beginning of the file (first two lines) old_sync = $stdout.sync $stdout.sync = true and a the end of the file (last line) $stdout.sync = old_sync.
The call to the IO#sync= method set the sync mode to true. This cause that all output is immediately flushed, at the end of the script we restore its value to its original old value, see Ruby documentation for details.
In summary:
old_sync = $stdout.sync # cache old value
$stdout.sync = true # set mode to true
# your scripting staff
$stdout.sync = old_sync # restore old value
If this trick works at least you know the reason for the weird behaviour. You can find some explanation also in this SO post.

Ruby Project - Prevent a ruby file from directly being called from OS command line

I am doing a demo command line project in Ruby. The structure is like this:
/ROOT_DIR
init.rb
/SCRIPT_DIR
(other scripts and files)
I want users to only go into the application using init.rb, but as it stands, anyone can go into the sub-folder and call other ruby scripts directly.
Questions:
What ways can above scenario be prevented?
If I was to use directory permissions, would it get reset when running the code from a Windows machine to on Linux machine?
Is there anything that can be included in Ruby files itself to prevent it from being directly called from OS command line?
You can't do this with file permissions, since the user needs to read the files; removing the read permission means you can't include it either. Removing the execute permission is useful to signal that these file aren't intended to be executed, but won't prevent people from typing ruby incl.rb.
The easiest way is probably to set a global variable in the init.rb script:
#!/usr/bin/env ruby
FROM_INIT = true
require './incl.rb'
puts 'This is init!'
And then check if this variable is defined in the included incl.rb file:
unless defined? FROM_INIT
puts 'Must be called from init.rb'
exit 0
end
puts 'This is incl!'
A second method might be checking the value of $PROGRAM_NAME in incl.rb; this stores the current program name (like argv[0] in many other languages):
unless $PROGRAM_NAME.end_with? 'init.rb'
puts 'Must be called from init.rb'
exit 0
end
I don't recommend this though, as it's not very future-proof; what if you want to rename init.rb or make a second script?

After reading a file ruby leaves it open/locked on Windows XP

I read a text file to get some info from it and later on I need to rename the directory that the file sits in. I am not able do to that because the file is locked. If I comment out the function that reads from the file or if I manually unlock the file (unlocker utility) everything goes fine.
I am on ruby 1.8.7 (2010-08-16 patchlevel 302) [i386-mingw32]
This line leaves the file open File.open(file).readlines.each{|line|
These two lines leave the file open
my_file=File.open(file,"r")
my_file.collect {|line|
unless I close the file at the end using my_file.close
The man for core 1.8.7 of IO.html#method-c-open states
If the optional code block is given, it will be passed io as an argument, and the IO object will automatically be closed when the block terminates.
So I don't understand why the file is still open.
What would be the one line code in 1.8.7 to read a text file and close it automatically?
The documentation is clear. However, you're passing the block to collect. And since you're not passing it to open, you are responsible for closing the file.
To have file auto-closed, try this:
File.open(file,"r") do |f|
f.collect # or whatever
end
Try passing the block directly to the "open" call:
File.open(file, 'r') do |f|
f.each_line do |line|
# Process each line
end
end
Or if you just want the file contents in a single shot then try this:
lines = File.read(file).split(/\r?\n/)
If you want the block to close the file automagically without passing the file handle to a block, you can use the IO#readlines method of your File object.
array_of_lines = File.readlines('/tmp/foo')
This will read the entire contents of the file, then close the file handle. It's a good option whenever a file is small enough to fit easily into memory.

Ruby System Call Executing Before Script Finishes

I have a Ruby script that produces a Latex document using an erb template. After the .tex file has been generated, I'd like to make a system call to compile the document with pdflatex. Here are the bones of the script:
class Book
# initialize the class, query a database to get attributes, create the book, etc.
end
my_book = Book.new
tex_file = File.open("/path/to/raw/tex/template")
template = ERB.new(tex_file.read)
f = File.new("/path/to/tex/output.tex")
f.puts template.result
system "pdflatex /path/to/tex/output.tex"
The system line puts me in interactive tex input mode, as if the document were empty. If I remove the call, the document is generated as normal. How can I ensure that the system call isn't made until after the document is generated? In the meantime I'm just using a bash script that calls the ruby script and then pdflatex to get around the issue.
The File.new will open a new stream that won't be closed (saved to disk) until the script ends of until you manually close it.
This should work:
...
f = File.new("/path/to/tex/output.tex")
f.puts template.result
f.close
system "pdflatex /path/to/tex/output.tex"
Or a more friendly way:
...
File.open("/path/to/tex/output.tex", 'w') do |f|
f.puts template.result
end
system "pdflatex /path/to/tex/output.tex"
The File.open with a block will open the stream, make the stream accessible via the block variable (f in this example) and auto-close the stream after the block execution. The 'w' will open or create the file (if the file already exists the content will be erased => The file will be truncated)

How to read an open file in Ruby

I want to be able to read a currently open file. The test.rb is sending its output to test.log which I want to be able to read and ultimately send via email.
I am running this using cron:
*/5 * * * /tmp/test.rb > /tmp/log/test.log 2>&1
I have something like this in test.rb:
#!/usr/bin/ruby
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
When I run this code, it only gives me this output:
Start
End
I would expect the output to be something like this:
Start
Start (from the reading of the test.log since it should have the word start already)
End
Ok, you're trying to do several things at once, and I suspect you didn't systematically test before moving from one step to the next.
First we're going to clean up your code:
def read_file(file_name)
file = File.open(file_name, "r")
data = file.read
file.close
return data
end
puts "Start"
puts read_file("/tmp/log/test.log")
puts "End"
can be replaced with:
puts "Start"
puts File.read("./test.log")
puts "End"
It's plain and simple; There's no need for a method or anything complicated... yet.
Note that for ease of testing I'm working with a file in the current directory. To put some content in it I'll simply do:
echo "foo" > ./test.log
Running the test code gives me...
Greg:Desktop greg$ ruby test.rb
Start
foo
End
so I know the code is reading and printing correctly.
Now we can test what would go into the crontab, before we deal with its madness:
Greg:Desktop greg$ ruby test.rb > ./test.log
Greg:Desktop greg$
Hmm. No output. Something is broken with that. We knew there was content in the file previously, so what happened?
Greg:Desktop greg$ cat ./test.log
Start
End
Cat'ing the file shows it has the "Start" and "End" output of the code, but the part that should have been read and output is now missing.
What happening is that the shell truncated "test.log" just before it passed control to Ruby, which then opened and executed the code, which opened the now empty file to print it. In other words, you're asking the shell to truncate (empty) it just before you read it.
The fix is to read from a different file than you're going to write to, if you're trying to do something with the contents of it. If you're not trying to do something with its contents then there's no point in reading it with Ruby just to write it to a different file: We have cp and/or mv to do those things for us witout Ruby being involved. So, this makes more sense if we're going to do something with the contents:
ruby test.rb > ./test.log.out
I'll reset the file contents using echo "foo" > ./test.log, and cat'ing it showed 'foo', so I'm ready to try the redirection test again:
Greg:Desktop greg$ ruby test.rb > ./test.log.out
Greg:Desktop greg$ cat test.log.out
Start
foo
End
That time it worked. Trying it again has the same result, so I won't show the results here.
If you're going to email the file you could add that code at this point. Replacing the puts in the puts File.read('./test.log') line with an assignment to a variable will store the file's content:
contents = File.read('./test.log')
Then you can use contents as the body of a email. (And, rather than use Ruby for all of this I'd probably do it using mail or mailx or pipe it directly to sendmail, using the command-line and shell, but that's your call.)
At this point things are in a good position to add the command to crontab, using the same command as used on the command-line. Because it's running in cron, and errors can happen that we'd want to know about, we'd add the 2>&1 redirect to capture STDERR also, just as you did before. Just remember that you can NOT write to the same file you're going to read from or you'll have an empty file to read.
That's enough to get your app working.
class FileLineRead
File.open("file_line_read.txt") do |file|
file.each do |line|
phone_number = line.gsub(/\n/,'')
user = User.find_by_phone_number(line)
user.destroy unless user.nil?
end
end
end
open file
read line
DB Select
DB Update
In the cron job you have already opened and cleared test.log (via redirection) before you have read it in the Ruby script.
Why not do both the read and write in Ruby?
It may be a permissions issue or the file may not exist.
f = File.open("test","r")
puts f.read()
f.close()
The above will read the file test. If the file exists in the current directory
The problem is, as I can see, already solved by Slomojo. I'll only add:
to read and print a text file in Ruby, just:
puts File.read("/tmp/log/test.log")

Resources