Parsing arguments with defaults and flags - ruby

I've got a ruby bin with some arguments, namely -s, -c and -r (short for scrape, create and run). Now I'd like to set some defaults to scrape and create ('.' in both cases), but if I use :default in trollop, I can't check wherever that argument is set or not.
project --scrape
should be equivalent to
project --scrape .
how to achieve that?
And while at it, how do I make
project target
to be equivalent with
project --run target
?

You can modify ARGV before Trollop processes it. Your best bet would probably be to scan the input arguments, apply some basic transformations, and then run Trollop.
For example:
args = ARGV.split
idx = args.index '--scrape'
if idx != nil
if idx < args.length
if args[idx + 1][0..1] == '--'
args=args[0..idx] + ['.'] + args[idx+1..-1]
end
else
if args[idx + 1][0..1] == '--'
args << '.'
end
end
end
This snippet should check for --scrape with no parameter following it and add in a '.' in that case. You can do something similar to check for the omitted --run parameter. When you are done making your modifications, use args.join(' ') to put the arguments back together into a string. Assign this new string to ARGV, and then set Trollop loose.

Related

Ruby file renamer

this is a text file renamer i made, you throw the file in a certain folder and the program renames them to file1.txt, file2.txt, etc
it gets the job done but it's got two problems
it gives me this error no implicit conversion of nil into String error
if i add new files into the folder where there's already organized files, they're all deleted and a new file is created
what's causing these problems?
i=0
Dir.chdir 'C:\Users\anon\Desktop\newfolder'
arr = Dir.entries('C:\Users\anon\Desktop\newfolder')
for i in 2..arr.count
if (File.basename(arr[i]) == 'file'+((i-1).to_s)+'.txt')
puts (arr[i]+' is already renamed to '+'file'+i.to_s)
else
File.rename(arr[i],'file'+((i-1).to_s)+'.txt')
end
end
There are two main problems in your program.
The first is that you are using an out of bounds value in the array arr. Try this a = [1,2,3]; a[a.count] and you will get nil because you are trying at access a[3] but the last element in the array has index 2.
Then, you are using as indexes for names fileINDEX.txt always 2...foobar without taking into account that some indexes may be already used in your directory.
Extra problem, you are using Dir.entries, this in my OS gives regular entries more . and .. which should be managed properly, they are not what you want to manipulate.
So, I wrote you a little script, I hope you find it readable, to me it works. You can improve it for sure! (p.s. I am under Linux OS).
# Global var only to stress its importance
$dir = "/home/p/tmp/t1"
Dir.chdir($dir)
# get list of files
fnames = Dir.glob "*"
# get the max index "fileINDEX.txt" already used in the directory
takenIndexes = []
fnames.each do |f|
if f.match /^file(\d+).txt/ then takenIndexes.push $1.to_i; end
end
# get the first free index available
firstFreeIndex = 1
firstFreeIndex = (takenIndexes.max + 1) if takenIndexes.length > 0
# get a range of fresh indexes for possible use
idxs = firstFreeIndex..(firstFreeIndex + (fnames.length))
# i transform the range to list and reverse the order because i want
# to use "pop" to get and remove them.
idxs = idxs.to_a
idxs.reverse!
# rename the files needing to be renamed
puts "--- Renamed files ----"
fnames.each do |f|
# if file has already the wanted format then move to next iteration
next if f.match /^file\d+.txt/
newName = "file" + idxs.pop.to_s + ".txt"
puts "rename: #{f} ---> #{newName}"
File.rename(f, newName)
end

How to get access to command-line arguments in Nim?

How can I access command line arguments in Nim?
The documentation shows only how to run the compiled Nim code with command line arguments
nim compile --run greetings.nim arg1 arg2
but I didn't find how to use their values in code.
Here's an example that prints the number of arguments and the first argument:
import os
echo paramCount(), " ", paramStr(1)
Personally I find paramCount and paramStr a bit confusing to work with, because the paramCount value differs from C conventions (see the document links).
Fortunately, there are additional convenient functions which do not require to be aware of the conventions:
commandLineParams returns a seq of only command line params.
getAppFilename returns the executable file name (what is argv[0] in the C world).
I have not checked when it was added, but the parseopt seems to me, the default and the best way for this.
commandLineParams isn't available on Posix.
os.commandLineParams() returns a sequence of command-line arguments provided to the program.
os.quoteShellCommand(<openArray[string]>) takes a sequence of command-line arguments and turns it into a single string with quotes around items containing spaces, so the string can be parsed properly.
parseopt.initOptParser(<string>) takes a full command-line string and parses it, returning an OptParser object.
parseopt.getopt(<OptParser>) is an iterator that yields parsed argument info.
You can combine them to parse a program's input arguments:
import std/[os, parseopt]
proc writeHelp() = discard
proc writeVersion() = discard
var positionalArgs = newSeq[string]()
var directories = newSeq[string]()
var optparser = initOptParser(quoteShellCommand(commandLineParams()))
for kind, key, val in optparser.getopt():
case kind
of cmdArgument:
positionalArgs.add(key)
of cmdLongOption, cmdShortOption:
case key
of "help", "h": writeHelp()
of "version", "v": writeVersion()
of "dir", "d":
directories.add(val)
of cmdEnd: assert(false) # cannot happen
echo "positionalArgs: ", positionalArgs
echo "directories: ", directories
Running this with nim c -r main.nim -d:foo --dir:bar dir1 dir2 dir3 prints:
positionalArgs: #["dir1", "dir2", "dir3"]
directories: #["foo", "bar"]

Renaming all subdirectories using Ruby

I have a deeply nested folder structure on a Windows 7 machine. Windows refuses to delete the directories as their names are too long. I want to rename all subfolders to something like 2 in the hope that it will be short enough to be deleted. This is my script:
#count = 0
Dir.glob("**/*") do |file| #find src files in current folder and all subfolders
if File.directory?(file)
File.rename(file, File.dirname(file) + File::SEPARATOR + "2")
#count += 1
end
end
puts #count
When the script runs, instead of renaming all sub-directories, it changes one more sub-directory, gradually going one level deeper each time. I.e., the output from running the script at the moment is:
C:\>renamer.rb
30
C:\>renamer.rb
31
C:\>renamer.rb
32
I'm confused as to why this is happening and would appreciate any input.
Am I taking the correct approach? I assume Ruby's recursive directory deletion methods would fail. However, when I try and execute
require "FileUtils"
FileUtils.remove_dir ("2", force = true)
I get the error
syntax error, unexpected ',', expecting ')'
FileUtils.remove_dir ("2", force = true)
^
syntax error, unexpected ')', expecting end-of-input
FileUtils.remove_dir ("2", force = true)
^
The problem is that Dir.glob("**/*") returns an array like this:
['folder', 'folder/sub', 'folder/sub/sub']
Now when you do:
File.rename(file, File.dirname(file) + File::SEPARATOR + "2")
it will rename folder, but when it reaches folder/sub, that doesn't exist anymore, because you have renamed folder to 2: it will be 2/sub instead of folder/sub. The solution is to reverse the array. This starts the renaming process on the deepest level and works its way up to the top level:
Dir.glob("**/*").reverse.each do |file|
# rest of your code can stay the same
end
As for your second problem, instead of:
FileUtils.remove_dir ("2", force = true)
You should use:
FileUtils.remove_dir("2", true)
First of all, make sure there is no space between remove_dir and (. That's what's causing the error.
Also force is the name of the parameter and by default it's false. That's why you see force = false in the API. If you want force to be true you can simply pass true to the function, like I show above.

How to write a Ruby command line app that supports tab completion?

I want to write a command line app, a shell if you will, in Ruby.
I want the user to be able to press Tab at certain points and offer completion of values.
How do I do this? What library must I use? Can you point me to some code examples?
Ah, it seems the standard library is my friend after all. What I was looking for is the Readline library.
Doc and examples here: http://www.ruby-doc.org/stdlib-1.9.3/libdoc/readline/rdoc/Readline.html
In particular, this is a good example from that page to show how completion works:
require 'readline'
LIST = [
'search', 'download', 'open',
'help', 'history', 'quit',
'url', 'next', 'clear',
'prev', 'past'
].sort
comp = proc { |s| LIST.grep(/^#{Regexp.escape(s)}/) }
Readline.completion_append_character = " "
Readline.completion_proc = comp
while line = Readline.readline('> ', true)
p line
end
NOTE: The proc receives only the last word entered. If you want the whole line typed so far (because you want to do context-specific completion), add the following line to the above code:
Readline.completer_word_break_characters = "" #Pass whole line to proc each time
(This is by default set to a list of characters that represent word boundaries and causes only the last word to be passed into your proc).
The Readline library is excellent, I've used it many times. But, if you're making it just for the fun of it, you can also roll your own completion.
Here's a simple completion script:
require 'io/console' # Ruby 1.9
require 'abbrev'
word = ""
#completions = Abbrev.abbrev([
"function",
"begin"
])
while (char = $stdin.getch) != "\r"
word += char
word = "" if char == " "
if char == "\t"
if comp = #completions[word = word[0..-2]]
print comp[word.length..-1]
end
else
print char
end
end
puts
Some ruby projects can help you create completion without writing complex code:
https://github.com/DannyBen/completely
https://github.com/drnic/tabtab
Well, I suggest that you use Emacs to run your command line Ruby app. In Emacs, my SO friends just recently helped me to solve the Autocomplete tab completion (here and here). Autocomplete seems to be the most intelligent word completion tool to date.

Using Ruby to automate a large directory system

So I have the following little script to make a file setup for organizing reports that we get.
#This script is to create a file structure for our survey data
require 'fileutils'
f = File.open('CustomerList.txt') or die "Unable to open file..."
a = f.readlines
x = 0
while a[x] != nil
Customer = a[x]
FileUtils.mkdir_p(Customer + "/foo/bar/orders")
FileUtils.mkdir_p(Customer + "/foo/bar/employees")
FileUtils.mkdir_p(Customer + "/foo/bar/comments")
x += 1
end
Everything seems to work before the while, but I keep getting:
'mkdir': Invalid argument - Cust001_JohnJacobSmith(JJS) (Errno::EINVAL)
Which would be the first line from the CustomerList.txt. Do I need to do something to the array entry to be considered a string? Am I mismatching variable types or something?
Thanks in advance.
The following worked for me:
IO.foreach('CustomerList.txt') do |customer|
customer.chomp!
["orders", "employees", "comments"].each do |dir|
FileUtils.mkdir_p("#{customer}/foo/bar/#{dir}")
end
end
with data like so:
$ cat CustomerList.txt
Cust001_JohnJacobSmith(JJS)
Cust003_JohnJacobSmith(JJS)
Cust002_JohnJacobSmith(JJS)
A few things to make it more like the ruby way:
Use blocks when opening a file or iterating through arrays, that way you don't need to worry about closing the file or accessing the array directly.
As noted by #inger, local vars start with lower case, customer.
When you want the value of a variable in a string usign #{} is more rubinic than concatenating with +.
Also note that we took off the trailing newline using chomp! (which changes the var in place, noted by the trailing ! on the method name)

Resources